diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx index 2e93c057..837f913e 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/create/page.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { notFound } from 'next/navigation'; import { BoardRouteParameters } from '@/app/lib/types'; import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data'; import { parseBoardRouteParams } from '@/app/lib/url-utils'; @@ -37,49 +38,54 @@ function getMoonBoardHoldSetImages(layoutKey: MoonBoardLayoutKey, setIds: number } export default async function CreateClimbPage(props: CreateClimbPageProps) { - const [params, searchParams] = await Promise.all([props.params, props.searchParams]); + try { + const [params, searchParams] = await Promise.all([props.params, props.searchParams]); - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); - let parsedParams; + let parsedParams; - if (hasNumericParams) { - parsedParams = parseBoardRouteParams(params); - } else { - parsedParams = await parseBoardRouteParamsWithSlugs(params); - } + if (hasNumericParams) { + parsedParams = parseBoardRouteParams(params); + } else { + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } + + // Handle MoonBoard separately (no database, different renderer) + if (parsedParams.board_name === 'moonboard') { + const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id); + if (!layoutInfo) { + return
Invalid MoonBoard layout
; + } - // Handle MoonBoard separately (no database, different renderer) - if (parsedParams.board_name === 'moonboard') { - const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id); - if (!layoutInfo) { - return
Invalid MoonBoard layout
; + const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids); + + return ( + + ); } - const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids); + // Aurora boards (kilter, tension) - use database + const boardDetails = await getBoardDetails(parsedParams); return ( - ); + } catch (error) { + console.error('Error in create page:', error); + notFound(); } - - // Aurora boards (kilter, tension) - use database - const boardDetails = await getBoardDetails(parsedParams); - - return ( - - ); } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx index ce909b71..92159f4f 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/import/page.tsx @@ -9,7 +9,7 @@ import { MoonBoardLayoutKey, } from '@/app/lib/moonboard-config'; import { Metadata } from 'next'; -import { redirect } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; export const metadata: Metadata = { title: 'Import Climbs | Boardsesh', @@ -35,40 +35,45 @@ function getMoonBoardHoldSetImages(layoutKey: MoonBoardLayoutKey, setIds: number } export default async function ImportPage(props: ImportPageProps) { - const params = await props.params; + try { + const params = await props.params; - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); - let parsedParams; + let parsedParams; - if (hasNumericParams) { - parsedParams = parseBoardRouteParams(params); - } else { - parsedParams = await parseBoardRouteParamsWithSlugs(params); - } + if (hasNumericParams) { + parsedParams = parseBoardRouteParams(params); + } else { + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } - // Only MoonBoard supports bulk import for now - if (parsedParams.board_name !== 'moonboard') { - // Redirect to the board's climb list page - redirect(`/${params.board_name}/${params.layout_id}/${params.size_id}/${params.set_ids}/${params.angle}`); - } + // Only MoonBoard supports bulk import for now + if (parsedParams.board_name !== 'moonboard') { + // Redirect to the board's climb list page + redirect(`/${params.board_name}/${params.layout_id}/${params.size_id}/${params.set_ids}/${params.angle}`); + } - const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id); - if (!layoutInfo) { - return
Invalid MoonBoard layout
; - } + const layoutInfo = getMoonBoardLayoutInfo(parsedParams.layout_id); + if (!layoutInfo) { + return
Invalid MoonBoard layout
; + } - const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids); + const holdSetImages = getMoonBoardHoldSetImages(layoutInfo.layoutKey, parsedParams.set_ids); - return ( - - ); + return ( + + ); + } catch (error) { + console.error('Error in import page:', error); + notFound(); + } } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx index 9f4697c9..280a28a9 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/layout.tsx @@ -4,7 +4,7 @@ import { Affix, Layout } from 'antd'; import { ParsedBoardRouteParameters, BoardRouteParameters, BoardDetails } from '@/app/lib/types'; import { parseBoardRouteParams, constructClimbListWithSlugs } from '@/app/lib/url-utils'; import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server'; -import { permanentRedirect } from 'next/navigation'; +import { notFound, permanentRedirect } from 'next/navigation'; import { Content } from 'antd/es/layout/layout'; import QueueControlBar from '@/app/components/queue-control/queue-control-bar'; import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data'; @@ -103,80 +103,85 @@ interface BoardLayoutProps { } export default async function BoardLayout(props: PropsWithChildren) { - const params = await props.params; + try { + const params = await props.params; + + const { children } = props; + + // Parse the route parameters + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); - const { children } = props; + let parsedParams: ParsedBoardRouteParameters; - // Parse the route parameters - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); + if (hasNumericParams) { + // For old URLs, use the simple parsing function first + parsedParams = parseBoardRouteParams(params); - let parsedParams: ParsedBoardRouteParameters; + // Redirect old URLs to new slug format + const boardDetails = getBoardDetailsUniversal(parsedParams); + + if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { + const newUrl = constructClimbListWithSlugs( + boardDetails.board_name, + boardDetails.layout_name, + boardDetails.size_name, + boardDetails.size_description, + boardDetails.set_names, + parsedParams.angle, + ); + + permanentRedirect(newUrl); + } + } else { + // For new URLs, use the slug parsing function + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } - if (hasNumericParams) { - // For old URLs, use the simple parsing function first - parsedParams = parseBoardRouteParams(params); + const { board_name, angle } = parsedParams; - // Redirect old URLs to new slug format + // Fetch the board details server-side const boardDetails = getBoardDetailsUniversal(parsedParams); - if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { - const newUrl = constructClimbListWithSlugs( - boardDetails.board_name, - boardDetails.layout_name, - boardDetails.size_name, - boardDetails.size_description, - boardDetails.set_names, - parsedParams.angle, - ); - - permanentRedirect(newUrl); - } - } else { - // For new URLs, use the slug parsing function - parsedParams = await parseBoardRouteParamsWithSlugs(params); + return ( + + + + + + + + + }> + {children} + + + + + + + + + + + + ); + } catch (error) { + console.error('Error in board layout:', error); + notFound(); } - - const { board_name, angle } = parsedParams; - - // Fetch the board details server-side - const boardDetails = getBoardDetailsUniversal(parsedParams); - - return ( - - - - - - - - - }> - {children} - - - - - - - - - - - - ); } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx index 1a9ed9f4..de61d1fa 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout.tsx @@ -6,7 +6,7 @@ import { BoardRouteParameters, ParsedBoardRouteParameters } from '@/app/lib/type import { parseBoardRouteParams, constructClimbListWithSlugs } from '@/app/lib/url-utils'; import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server'; import { getBoardDetails } from '@/app/lib/__generated__/product-sizes-data'; -import { permanentRedirect } from 'next/navigation'; +import { notFound, permanentRedirect } from 'next/navigation'; import ListLayoutClient from './layout-client'; interface LayoutProps { @@ -14,43 +14,48 @@ interface LayoutProps { } export default async function ListLayout(props: PropsWithChildren) { - const params = await props.params; - - const { children } = props; - - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); - - let parsedParams: ParsedBoardRouteParameters; - - if (hasNumericParams) { - // For old URLs, use the simple parsing function first - parsedParams = parseBoardRouteParams(params); + try { + const params = await props.params; + + const { children } = props; + + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); + + let parsedParams: ParsedBoardRouteParameters; + + if (hasNumericParams) { + // For old URLs, use the simple parsing function first + parsedParams = parseBoardRouteParams(params); + + // Redirect old URLs to new slug format + const boardDetails = await getBoardDetails(parsedParams); + + if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { + const newUrl = constructClimbListWithSlugs( + boardDetails.board_name, + boardDetails.layout_name, + boardDetails.size_name, + boardDetails.size_description, + boardDetails.set_names, + parsedParams.angle, + ); + + permanentRedirect(newUrl); + } + } else { + // For new URLs, use the slug parsing function + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } - // Redirect old URLs to new slug format + // Fetch the climbs and board details server-side const boardDetails = await getBoardDetails(parsedParams); - if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { - const newUrl = constructClimbListWithSlugs( - boardDetails.board_name, - boardDetails.layout_name, - boardDetails.size_name, - boardDetails.size_description, - boardDetails.set_names, - parsedParams.angle, - ); - - permanentRedirect(newUrl); - } - } else { - // For new URLs, use the slug parsing function - parsedParams = await parseBoardRouteParamsWithSlugs(params); + return {children}; + } catch (error) { + console.error('Error in list layout:', error); + notFound(); } - - // Fetch the climbs and board details server-side - const boardDetails = await getBoardDetails(parsedParams); - - return {children}; } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx index ace45492..f6135833 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/page.tsx @@ -18,109 +18,106 @@ export default async function DynamicResultsPage(props: { params: Promise; searchParams: Promise; }) { - const searchParams = await props.searchParams; - const params = await props.params; - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); - - let parsedParams; - - if (hasNumericParams) { - // For old URLs, use the simple parsing function first - parsedParams = parseBoardRouteParams(params); - - // Redirect old URLs to new slug format - const boardDetails = await getBoardDetails(parsedParams); - - if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { - const newUrl = constructClimbListWithSlugs( - boardDetails.board_name, - boardDetails.layout_name, - boardDetails.size_name, - boardDetails.size_description, - boardDetails.set_names, - parsedParams.angle, - ); - - // Preserve search parameters - const searchString = new URLSearchParams( - Object.entries(searchParams).reduce( - (acc, [key, value]) => { - if (value !== undefined) { - acc[key] = String(value); - } - return acc; - }, - {} as Record, - ), - ).toString(); - const finalUrl = searchString ? `${newUrl}?${searchString}` : newUrl; - - permanentRedirect(finalUrl); + try { + const searchParams = await props.searchParams; + const params = await props.params; + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); + + let parsedParams; + + if (hasNumericParams) { + // For old URLs, use the simple parsing function first + parsedParams = parseBoardRouteParams(params); + + // Redirect old URLs to new slug format + const boardDetails = await getBoardDetails(parsedParams); + + if (boardDetails.layout_name && boardDetails.size_name && boardDetails.set_names) { + const newUrl = constructClimbListWithSlugs( + boardDetails.board_name, + boardDetails.layout_name, + boardDetails.size_name, + boardDetails.size_description, + boardDetails.set_names, + parsedParams.angle, + ); + + // Preserve search parameters + const searchString = new URLSearchParams( + Object.entries(searchParams).reduce( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = String(value); + } + return acc; + }, + {} as Record, + ), + ).toString(); + const finalUrl = searchString ? `${newUrl}?${searchString}` : newUrl; + + permanentRedirect(finalUrl); + } + } else { + // For new URLs, use the slug parsing function + parsedParams = await parseBoardRouteParamsWithSlugs(params); } - } else { - // For new URLs, use the slug parsing function - parsedParams = await parseBoardRouteParamsWithSlugs(params); - } - - const searchParamsObject: SearchRequestPagination = parsedRouteSearchParamsToSearchParams(searchParams); - // For the SSR version we increase the pageSize so it also gets whatever page number - // is in the search params. Without this, it would load the SSR version of the page on page 2 - // which would then flicker once SWR runs on the client. - const requestedPageSize = (Number(searchParamsObject.page) + 1) * Number(searchParamsObject.pageSize); - - // Enforce max page size to prevent excessive database queries - searchParamsObject.pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE); - searchParamsObject.page = 0; - - // Build the search input for caching - // Note: We only cache non-personalized queries (no auth-dependent filters) - // User-specific filters (hideAttempted, hideCompleted, etc.) are applied client-side - const searchInput = { - boardName: parsedParams.board_name, - layoutId: parsedParams.layout_id, - sizeId: parsedParams.size_id, - setIds: parsedParams.set_ids.join(','), - angle: parsedParams.angle, - page: searchParamsObject.page, - pageSize: searchParamsObject.pageSize, - gradeAccuracy: searchParamsObject.gradeAccuracy ? String(searchParamsObject.gradeAccuracy) : undefined, - minGrade: searchParamsObject.minGrade || undefined, - maxGrade: searchParamsObject.maxGrade || undefined, - minAscents: searchParamsObject.minAscents || undefined, - sortBy: searchParamsObject.sortBy || 'ascents', - sortOrder: searchParamsObject.sortOrder || 'desc', - name: searchParamsObject.name || undefined, - setter: searchParamsObject.settername && searchParamsObject.settername.length > 0 ? searchParamsObject.settername : undefined, - }; - - // Check if this is a default search (no custom filters applied) - // Default searches can be cached much longer (30 days vs 1 hour) - const isDefaultSearch = - !searchParamsObject.gradeAccuracy && - !searchParamsObject.minGrade && - !searchParamsObject.maxGrade && - !searchParamsObject.minAscents && - !searchParamsObject.name && - (!searchParamsObject.settername || searchParamsObject.settername.length === 0) && - (searchParamsObject.sortBy || 'ascents') === 'ascents' && - (searchParamsObject.sortOrder || 'desc') === 'desc'; - - let searchResponse: ClimbSearchResponse; - let boardDetails: BoardDetails; - - try { - [searchResponse, boardDetails] = await Promise.all([ + const searchParamsObject: SearchRequestPagination = parsedRouteSearchParamsToSearchParams(searchParams); + + // For the SSR version we increase the pageSize so it also gets whatever page number + // is in the search params. Without this, it would load the SSR version of the page on page 2 + // which would then flicker once SWR runs on the client. + const requestedPageSize = (Number(searchParamsObject.page) + 1) * Number(searchParamsObject.pageSize); + + // Enforce max page size to prevent excessive database queries + searchParamsObject.pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE); + searchParamsObject.page = 0; + + // Build the search input for caching + // Note: We only cache non-personalized queries (no auth-dependent filters) + // User-specific filters (hideAttempted, hideCompleted, etc.) are applied client-side + const searchInput = { + boardName: parsedParams.board_name, + layoutId: parsedParams.layout_id, + sizeId: parsedParams.size_id, + setIds: parsedParams.set_ids.join(','), + angle: parsedParams.angle, + page: searchParamsObject.page, + pageSize: searchParamsObject.pageSize, + gradeAccuracy: searchParamsObject.gradeAccuracy ? String(searchParamsObject.gradeAccuracy) : undefined, + minGrade: searchParamsObject.minGrade || undefined, + maxGrade: searchParamsObject.maxGrade || undefined, + minAscents: searchParamsObject.minAscents || undefined, + sortBy: searchParamsObject.sortBy || 'ascents', + sortOrder: searchParamsObject.sortOrder || 'desc', + name: searchParamsObject.name || undefined, + setter: searchParamsObject.settername && searchParamsObject.settername.length > 0 ? searchParamsObject.settername : undefined, + }; + + // Check if this is a default search (no custom filters applied) + // Default searches can be cached much longer (30 days vs 1 hour) + const isDefaultSearch = + !searchParamsObject.gradeAccuracy && + !searchParamsObject.minGrade && + !searchParamsObject.maxGrade && + !searchParamsObject.minAscents && + !searchParamsObject.name && + (!searchParamsObject.settername || searchParamsObject.settername.length === 0) && + (searchParamsObject.sortBy || 'ascents') === 'ascents' && + (searchParamsObject.sortOrder || 'desc') === 'desc'; + + const [searchResponse, boardDetails]: [ClimbSearchResponse, BoardDetails] = await Promise.all([ cachedSearchClimbs(SEARCH_CLIMBS, { input: searchInput }, isDefaultSearch), getBoardDetails(parsedParams), ]); + + return ; } catch (error) { - console.error('Error fetching results or climb:', error); + console.error('Error in list page:', error); notFound(); } - - return ; } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx index 8b77e22b..f3a41760 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/[climb_uuid]/page.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { notFound } from 'next/navigation'; import { BoardRouteParametersWithUuid } from '@/app/lib/types'; import { parseBoardRouteParams, extractUuidFromSlug, constructPlayUrlWithSlugs } from '@/app/lib/url-utils'; import { parseBoardRouteParamsWithSlugs } from '@/app/lib/url-utils.server'; @@ -79,46 +80,51 @@ export async function generateMetadata(props: { params: Promise; }): Promise { - const params = await props.params; + try { + const params = await props.params; - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); - let parsedParams; + let parsedParams; - if (hasNumericParams) { - parsedParams = parseBoardRouteParams({ - ...params, - climb_uuid: extractUuidFromSlug(params.climb_uuid), - }); - } else { - parsedParams = await parseBoardRouteParamsWithSlugs(params); - } + if (hasNumericParams) { + parsedParams = parseBoardRouteParams({ + ...params, + climb_uuid: extractUuidFromSlug(params.climb_uuid), + }); + } else { + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } - const boardDetails = await getBoardDetails(parsedParams); + const boardDetails = await getBoardDetails(parsedParams); - // Try to get the initial climb for SSR - let initialClimb = null; - try { - const climb = await getClimb(parsedParams); - if (climb) { - const litUpHoldsMap = convertLitUpHoldsStringToMap(climb.frames, parsedParams.board_name)[0]; - initialClimb = { - ...climb, - litUpHoldsMap, - }; + // Try to get the initial climb for SSR + let initialClimb = null; + try { + const climb = await getClimb(parsedParams); + if (climb) { + const litUpHoldsMap = convertLitUpHoldsStringToMap(climb.frames, parsedParams.board_name)[0]; + initialClimb = { + ...climb, + litUpHoldsMap, + }; + } + } catch { + // Climb will be loaded from queue context on client } - } catch { - // Climb will be loaded from queue context on client - } - return ( - - ); + return ( + + ); + } catch (error) { + console.error('Error in play page:', error); + notFound(); + } } diff --git a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx index 783e7ada..df295517 100644 --- a/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx +++ b/packages/web/app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/play/layout.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { PropsWithChildren } from 'react'; +import { notFound } from 'next/navigation'; import { BoardRouteParameters, ParsedBoardRouteParameters } from '@/app/lib/types'; import { parseBoardRouteParams } from '@/app/lib/url-utils'; @@ -12,23 +13,28 @@ interface LayoutProps { } export default async function PlayLayout(props: PropsWithChildren) { - const params = await props.params; - const { children } = props; + try { + const params = await props.params; + const { children } = props; - // Check if any parameters are in numeric format (old URLs) - const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => - param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), - ); + // Check if any parameters are in numeric format (old URLs) + const hasNumericParams = [params.layout_id, params.size_id, params.set_ids].some((param) => + param.includes(',') ? param.split(',').every((id) => /^\d+$/.test(id.trim())) : /^\d+$/.test(param), + ); - let parsedParams: ParsedBoardRouteParameters; + let parsedParams: ParsedBoardRouteParameters; - if (hasNumericParams) { - parsedParams = parseBoardRouteParams(params); - } else { - parsedParams = await parseBoardRouteParamsWithSlugs(params); - } + if (hasNumericParams) { + parsedParams = parseBoardRouteParams(params); + } else { + parsedParams = await parseBoardRouteParamsWithSlugs(params); + } - const boardDetails = await getBoardDetails(parsedParams); + const boardDetails = await getBoardDetails(parsedParams); - return {children}; + return {children}; + } catch (error) { + console.error('Error in play layout:', error); + notFound(); + } }