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
23 changes: 23 additions & 0 deletions client/__tests__/CalendarPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,29 @@ describe("CalendarPage", () => {
});
});

it("greys out past months and past releases in the year view", async () => {
mockGamesFetch([
{ ...baseGame, id: "game-5", title: "April Game", releaseDate: "2026-04-10" },
{ ...baseGame, id: "game-6", title: "Early May Game", releaseDate: "2026-05-03" },
{ ...baseGame, id: "game-7", title: "June Game", releaseDate: "2026-06-02" },
{ ...baseGame, id: "game-8", title: "Datetime Game", releaseDate: "2026-05-10T12:00:00Z" },
]);

renderPage();

const aprilGame = await screen.findByText("April Game");
const aprilCard = screen.getByRole("heading", { name: "April" }).closest(".bg-card");
const juneCard = screen.getByRole("heading", { name: "June" }).closest(".bg-card");

expect(aprilCard).toHaveClass("opacity-50");
expect(juneCard).not.toHaveClass("opacity-50");
// Inside a past month the card is already dimmed, so its badges stay as-is.
expect(aprilGame.closest("button")).not.toHaveClass("opacity-50");
expect(screen.getByText("Early May Game").closest("button")).toHaveClass("opacity-50");
expect(screen.getByText("June Game").closest("button")).not.toHaveClass("opacity-50");
expect(screen.getByText("Datetime Game").closest("button")).toHaveClass("opacity-50");
});

it("switches to week view and opens the download dialog from a visible game", async () => {
mockGamesFetch([{ ...baseGame, id: "game-4", title: "Week Hero", releaseDate: "2026-05-29" }]);

Expand Down
28 changes: 26 additions & 2 deletions client/src/pages/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export function formatDate(date: Date): string {
return `${y}-${m}-${d}`;
}

// Release dates are usually date-only ("YYYY-MM-DD"), but a claimed download can carry a full
// ISO datetime. Map those to the viewer's local calendar day before comparing with today.
function toLocalDateKey(releaseDate: string): string {
return /^\d{4}-\d{2}-\d{2}$/.test(releaseDate) ? releaseDate : formatDate(new Date(releaseDate));
}

function getMonthName(month: number): string {
return new Date(2000, month, 1).toLocaleDateString(undefined, { month: "long" });
}
Expand Down Expand Up @@ -262,6 +268,7 @@ export default function CalendarPage() {
currentDate={currentDate}
gamesByDate={gamesByDate}
onGameClick={handleGameClick}
today={today}
/>
)}
{viewMode === "month" && (
Expand Down Expand Up @@ -305,13 +312,17 @@ function YearView({
currentDate,
gamesByDate,
onGameClick,
today,
}: {
currentDate: Date;
gamesByDate: GamesByDate;
onGameClick: (game: Game) => void;
today: Date;
}) {
const year = currentDate.getFullYear();
const months = Array.from({ length: 12 }, (_, i) => i);
const todayKey = formatDate(today);
const currentMonthPrefix = todayKey.slice(0, 8);

// Pre-calculate entries once outside the render loop
const allGamesEntries = useMemo(() => Object.entries(gamesByDate), [gamesByDate]);
Expand All @@ -332,8 +343,16 @@ function YearView({
}
}

const isPastMonth = monthPrefix < currentMonthPrefix;

return (
<div key={month} className="bg-card border rounded-lg p-4">
<div
key={month}
className={cn(
"bg-card border rounded-lg p-4",
isPastMonth && "opacity-50 grayscale-[0.3]"
)}
>
<div className="flex items-center justify-between mb-3">
<h3 className="font-semibold">{getMonthName(month)}</h3>
{gameCount > 0 && (
Expand All @@ -353,7 +372,12 @@ function YearView({
})}
</div>
{games.map((game) => (
<GameBadge key={game.id} game={game} onClick={() => onGameClick(game)} />
<GameBadge
key={game.id}
game={game}
muted={!isPastMonth && toLocalDateKey(date) < todayKey}
onClick={() => onGameClick(game)}
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
))}
</div>
))
Expand Down
Loading