-
Notifications
You must be signed in to change notification settings - Fork 5
Redesign homepage leaderboard cards and add pantheon community race #328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4825814
f10a415
8e25c8a
a8e9e2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { type Metadata } from "next" | ||
| import { notFound } from "next/navigation" | ||
| import { LeaderboardSSR } from "~/app/leaderboards/LeaderboardSSR" | ||
| import { CloudflareActivitySplash } from "~/components/CloudflareImage" | ||
| import { getActivePantheonIds, PANTHEON_COMMUNITY_RACE_VERSION_ID } from "~/lib/manifest/pantheon" | ||
| import { baseMetadata } from "~/lib/metadata" | ||
| import { prefetchManifest } from "~/services/raidhub/prefetchRaidHubManifest" | ||
| import { Leaderboard } from "../../../Leaderboard" | ||
| import { Splash } from "../../../LeaderboardSplashComponents" | ||
|
|
||
| export const revalidate = 900 | ||
| export const dynamic = "force-static" | ||
| export const fetchCache = "default-no-store" | ||
|
|
||
| export async function generateMetadata(): Promise<Metadata> { | ||
| const manifest = await prefetchManifest() | ||
| const activityId = getActivePantheonIds(manifest)[0] | ||
| const activity = activityId != null ? manifest.activityDefinitions[activityId] : undefined | ||
| const version = manifest.versionDefinitions[PANTHEON_COMMUNITY_RACE_VERSION_ID] | ||
|
|
||
| if (!activity || version?.associatedActivityId == null) { | ||
| notFound() | ||
| } | ||
|
|
||
| const title = `${activity.name}: ${version.name} Community Race Leaderboard` | ||
| const description = `View community race placements for ${version.name} in ${activity.name}` | ||
|
|
||
| return { | ||
| title, | ||
| description, | ||
| keywords: [ | ||
| activity.name, | ||
| version.name, | ||
| "community race", | ||
| "pantheon", | ||
| ...baseMetadata.keywords | ||
| ], | ||
| openGraph: { | ||
| ...baseMetadata.openGraph, | ||
| title, | ||
| description | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export default async function Page({ searchParams }: { searchParams: Record<string, string> }) { | ||
| const manifest = await prefetchManifest() | ||
| const activityId = getActivePantheonIds(manifest)[0] | ||
| const activity = activityId != null ? manifest.activityDefinitions[activityId] : undefined | ||
| const version = manifest.versionDefinitions[PANTHEON_COMMUNITY_RACE_VERSION_ID] | ||
|
|
||
| if (!activity || version?.associatedActivityId == null) { | ||
| notFound() | ||
| } | ||
|
|
||
| return ( | ||
| <Leaderboard | ||
| heading={ | ||
| <Splash | ||
| tertiaryTitle={activity.name} | ||
| title={version.name} | ||
| subtitle="Community Race Leaderboard"> | ||
| <CloudflareActivitySplash | ||
| activityId={version.associatedActivityId} | ||
| versionId={version.id} | ||
| fill | ||
| className="z-[-1]" | ||
| /> | ||
|
Comment on lines
+63
to
+68
This comment was marked as outdated.
Sorry, something went wrong. |
||
| </Splash> | ||
| } | ||
| hasPages | ||
| hasSearch | ||
| external={false} | ||
| pageProps={{ | ||
| layout: "team", | ||
| queryKey: ["raidhub", "leaderboard", "pantheon-community-race"], | ||
| entriesPerPage: 50, | ||
| apiUrl: "/leaderboard/team/custom/pantheon-community-race", | ||
| params: null | ||
| }} | ||
| entries={ | ||
| <LeaderboardSSR | ||
| page={searchParams.page ?? "1"} | ||
| entriesPerPage={50} | ||
| apiUrl="/leaderboard/team/custom/pantheon-community-race" | ||
| params={null} | ||
| /> | ||
| } | ||
| /> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import Image, { type ImageLoader } from "next/image" | |
| import { useCallback, type ComponentPropsWithoutRef } from "react" | ||
| import { HomePageSplash } from "~/lib/activity-images" | ||
| import { VaultEmblems } from "~/lib/bungie-foundation-emblems" | ||
| import { cn } from "~/lib/tw" | ||
| import { type ImageSize } from "~/services/raidhub/types" | ||
| import { useRaidHubManifest } from "./providers/RaidHubManifestManager" | ||
|
|
||
|
|
@@ -125,31 +126,38 @@ export const CloudflareActivitySplash = ({ | |
| alt, | ||
| ...props | ||
| }: { activityId: number; versionId?: number } & StrippedImageProps) => { | ||
| const { getImageVariantsForActivity, getActivityDefinition, getVersionString } = | ||
| useRaidHubManifest() | ||
| const { | ||
| getImageVariantsForActivity, | ||
| getImageVariantsForVersion, | ||
| getActivityDefinition, | ||
| getVersionString | ||
| } = useRaidHubManifest() | ||
|
|
||
| const loader = useCallback<ImageLoader>( | ||
| ({ width, quality }) => { | ||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||
| const minWidth = (width * (quality || 75)) / 100 | ||
|
|
||
| const activityVariants = getImageVariantsForActivity(activityId) | ||
| if (!activityVariants?.length) { | ||
| const splashVariants = | ||
| versionId != null | ||
| ? getImageVariantsForVersion(versionId) | ||
| : getImageVariantsForActivity(activityId) | ||
| if (!splashVariants.length) { | ||
| return FallbackSplash | ||
| } | ||
|
|
||
| const availableSizes = new Set(activityVariants.map(c => c.size)) | ||
| const availableSizes = new Set(splashVariants.map(c => c.size)) | ||
|
|
||
| const variants = cloudflareVariants.filter(item => availableSizes.has(item.name)) | ||
| const size = ( | ||
| variants.find(item => item.w >= minWidth && availableSizes.has(item.name)) ?? | ||
| variants[variants.length - 1] | ||
| ).name | ||
| const content = activityVariants.find(c => c.size === size)! | ||
| const content = splashVariants.find(c => c.size === size)! | ||
|
Comment on lines
154
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The image loader may crash with a Suggested FixAdd a guard to handle the case where the filtered Prompt for AI Agent |
||
|
|
||
| return content.url | ||
| }, | ||
| [activityId, getImageVariantsForActivity] | ||
| [activityId, versionId, getImageVariantsForActivity, getImageVariantsForVersion] | ||
| ) | ||
|
|
||
| const activityDefinition = getActivityDefinition(activityId) | ||
|
|
@@ -159,5 +167,13 @@ export const CloudflareActivitySplash = ({ | |
| ? activityDefinition.name | ||
| : getVersionString(versionId ?? activityId)) | ||
|
|
||
| return <Image loader={loader} {...props} src="placeholder" alt={altText} /> | ||
| return ( | ||
| <Image | ||
| loader={loader} | ||
| {...props} | ||
| className={cn(props.fill && "object-cover object-[center_30%]", props.className)} | ||
| src="placeholder" | ||
| alt={altText} | ||
| /> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Calling
notFound()ingenerateMetadatafor a statically generated page (force-static) will cause the entire application build to fail if the necessary data is missing.Severity: CRITICAL
Suggested Fix
In
generateMetadata, instead of callingnotFound(), return fallback metadata (e.g., a generic title). Move the logic for checking data availability and callingnotFound()into the Page component itself. This ensures the page can still be statically generated, and the 404 is handled at request time.Prompt for AI Agent