Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/frontend/app/clan/[clanName]/ClanPlayerCount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import { useSearchParams } from 'next/navigation';

export function ClanPlayerCount({
activeCount,
totalCount,
}: {
activeCount: number;
totalCount: number;
}) {
const searchParams = useSearchParams();
const past = searchParams?.get('past');
const showPast = past === 'true' || past === '1';

return (
<span className="pr-4">
{activeCount} players
{showPast && <span className="text-gray-400 ml-1">({totalCount})</span>}
</span>
);
}
12 changes: 10 additions & 2 deletions apps/frontend/app/clan/[clanName]/PastMembersToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";

export function PastMembersToggle({ clanName, showPastMembers }: { clanName: string, showPastMembers: boolean }) {
const router = useRouter();
const searchParams = useSearchParams();

return (
<div className="flex justify-end px-8 mb-2">
Expand All @@ -12,7 +13,14 @@ export function PastMembersToggle({ clanName, showPastMembers }: { clanName: str
type="checkbox"
checked={showPastMembers}
onChange={(e) => {
const url = `/clan/${encodeURIComponent(clanName)}${e.target.checked ? '?past=true' : ''}`;
const params = new URLSearchParams(searchParams.toString());
if (e.target.checked) {
params.set('past', 'true');
} else {
params.delete('past');
}
const queryString = params.toString();
const url = `/clan/${encodeURIComponent(clanName)}${queryString ? `?${queryString}` : ''}`;
router.replace(url, { scroll: false });
}}
className="cursor-pointer"
Expand Down
9 changes: 8 additions & 1 deletion apps/frontend/app/clan/[clanName]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LayoutTabs } from './LayoutTabs';
import { paramsSchema } from './schema';
import { z } from 'zod';
import { formatPlayTime } from '../../../utils/format';
import { ClanPlayerCount } from './ClanPlayerCount';

export default async function Index({
params,
Expand All @@ -19,6 +20,9 @@ export default async function Index({
name: true,
playTime: true,
activePlayerCount: true,
_count: {
select: { clanPlayerInfos: true },
},
},
where: {
name: clanName,
Expand Down Expand Up @@ -51,7 +55,10 @@ export default async function Index({
<header className="flex flex-row px-20 gap-4 items-center">
<section className="flex flex-col gap-2 grow">
<h1 className="text-2xl font-bold">{clan.name}</h1>
<span className="pr-4">{`${clan.activePlayerCount} players`}</span>
<ClanPlayerCount
activeCount={clan.activePlayerCount}
totalCount={clan._count.clanPlayerInfos}
/>
</section>
<aside className="flex flex-col gap-2 text-right">
<p>
Expand Down
23 changes: 21 additions & 2 deletions apps/frontend/app/clan/[clanName]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { paramsSchema } from './schema';
import { z } from 'zod';
import { notFound } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import prisma from '../../../utils/prisma';
import { PlayerList } from '../../../components/PlayerList';
import { searchParamPageSchema } from '../../../utils/page';
Expand Down Expand Up @@ -90,11 +90,30 @@ export default async function Index({
return notFound();
}

const playerCount = showPastMembers ? clan._count.clanPlayerInfos : clan.activePlayerCount;
const maxPage = Math.ceil(playerCount / 100) || 1;

if (page > maxPage) {
// Generate new URL with the max possible page
const params = new URLSearchParams();
for (const [key, value] of Object.entries(searchParams)) {
if (value !== undefined) {
if (Array.isArray(value)) {
value.forEach((v) => params.append(key, v));
} else {
params.set(key, value);
}
}
}
params.set('page', maxPage.toString());
return redirect(`/clan/${encodeURIComponent(clanName)}?${params.toString()}`);
}

return (
<div className="flex flex-col gap-4">
<PastMembersToggle clanName={clanName} showPastMembers={showPastMembers} />
<PlayerList
playerCount={showPastMembers ? clan._count.clanPlayerInfos : clan.activePlayerCount}
playerCount={playerCount}
rankMethod={null}
showLastSeen={true}
players={clan.clanPlayerInfos.map((playerInfo, index) => ({
Expand Down
6 changes: 4 additions & 2 deletions apps/frontend/components/LastSeen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { encodeIp } from '../utils/encoding';
export function LastSeen({
lastSeenAt,
gameServers,
className: propClassName,
}: {
lastSeenAt: Date;
gameServers: {
ip: string;
port: number;
}[];
className?: string;
}) {
if (gameServers.length > 0) {
return (
Expand All @@ -21,7 +23,7 @@ export function LastSeen({
href={{
pathname: `/server/${encodeIp(gameServers[0].ip)}/${gameServers[0].port}`,
}}
className="text-[#43a700] font-bold hover:underline"
className={twMerge("text-[#43a700] font-bold hover:underline", propClassName)}
>
Online
</Link>
Expand All @@ -45,7 +47,7 @@ export function LastSeen({
}

return (
<span className={twMerge('truncate', className)}>
<span className={twMerge('truncate', className, propClassName)}>
{formatDurationShort(
intervalToDuration({ start: lastSeenAt, end: new Date() })
)}
Expand Down
23 changes: 13 additions & 10 deletions apps/frontend/components/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,23 @@ export function Pagination({ pageCount }: { pageCount: number }) {
}
}

const getQuery = (targetPage: number) => {
const q = { ...searchParams };
if (targetPage === 1) {
delete q.page;
} else {
q.page = String(targetPage);
}
return q;
};

return (
<div className="flex flex-row justify-between border rounded-md overflow-hidden text-sm divide-x">
<Button
label="Previous"
href={{
pathname,
query: currentPage === 2 ? {} : { page: currentPage - 1 },
query: getQuery(currentPage - 1),
}}
disabled={currentPage === 1}
/>
Expand All @@ -86,12 +96,7 @@ export function Pagination({ pageCount }: { pageCount: number }) {
prefetch={false}
href={{
pathname,
query:
page === 1
? {}
: {
page,
},
query: getQuery(page),
}}
className={twMerge(
'px-2',
Expand All @@ -111,9 +116,7 @@ export function Pagination({ pageCount }: { pageCount: number }) {
label="Next"
href={{
pathname,
query: {
page: currentPage + 1,
},
query: getQuery(currentPage + 1),
}}
disabled={currentPage === pageCount}
/>
Expand Down
18 changes: 13 additions & 5 deletions apps/frontend/components/PlayerList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,22 @@ export function PlayerList({
playerCount === undefined ? undefined : Math.ceil(playerCount / 100)
}
>
{players.map((player) => (
{players.map((player) => {
const rowClassName = player.isActiveClan === false ? 'text-gray-400' : '';

return (
<Fragment key={player.name}>
<ListCell alignRight label={formatInteger(player.rank)} />
<ListCell alignRight label={formatInteger(player.rank)} className={rowClassName} />
<ListCell
label={player.isActiveClan === false ? `${player.name} (Past)` : player.name}
className={player.isActiveClan === false ? 'text-gray-400' : ''}
className={rowClassName}
href={{
pathname: `/player/${encodeString(player.name)}`,
}}
/>
<ListCell
label={player.clan ?? ''}
className={rowClassName}
href={
player.clan === undefined
? undefined
Expand All @@ -96,6 +100,7 @@ export function PlayerList({
{rankMethod === RankMethod.ELO && (
<ListCell
alignRight
className={rowClassName}
label={
player.rating === undefined ? '' : formatInteger(player.rating)
}
Expand All @@ -104,22 +109,25 @@ export function PlayerList({
{rankMethod === RankMethod.TIME && (
<ListCell
alignRight
className={rowClassName}
label={
player.rating === undefined
? ''
: formatPlayTime(BigInt(-player.rating))
}
/>
)}
<ListCell alignRight label={formatPlayTime(player.playTime)} />
<ListCell alignRight label={formatPlayTime(player.playTime)} className={rowClassName} />
{showLastSeen && (
<LastSeen
lastSeenAt={player.lastSeenAt}
gameServers={player.gameServers}
className={rowClassName}
/>
)}
</Fragment>
))}
);
})}
</List>
);
}
Loading