generated from taylorbryant/gatsby-starter-tailwind
-
-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate climb page to Next 13 structure (#1195)
* migrate climb page to next13 structure * resolve to /climb page globally * add map link to main nav
- Loading branch information
Showing
30 changed files
with
441 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import { AreaPageContainer } from '@/app/(default)/components/ui/AreaPageContainer' | ||
import { DefaultPageContainer } from '@/app/(default)/components/ui/DefaultPageContainer' | ||
|
||
/** | ||
* Loading skeleton for /area/<id> page. | ||
*/ | ||
export default function Loading (): JSX.Element { | ||
return (<AreaPageContainer />) | ||
return (<DefaultPageContainer />) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
src/app/(default)/climb/[[...slug]]/components/ClimbData.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { ArrowsVertical } from '@phosphor-icons/react/dist/ssr' | ||
|
||
import RouteGradeChip from '@/components/ui/RouteGradeChip' | ||
import RouteTypeChips from '@/components/ui/RouteTypeChips' | ||
import { ArticleLastUpdate } from '@/components/edit/ArticleLastUpdate' | ||
import { ClimbType, AreaType } from '@/js/types' | ||
import Grade from '@/js/grades/Grade' | ||
import { removeTypenameFromDisciplines } from '@/js/utils' | ||
|
||
export const ClimbData: React.FC<ClimbType & Pick<AreaType, 'gradeContext'> & { isBoulder: boolean }> = (props) => { | ||
const { name, type, safety, length, grades, fa: legacyFA, authorMetadata, gradeContext, isBoulder } = props | ||
|
||
const sanitizedDisciplines = removeTypenameFromDisciplines(type) | ||
|
||
const gradeStr = new Grade( | ||
gradeContext, | ||
grades, | ||
sanitizedDisciplines, | ||
isBoulder | ||
).toString() | ||
return ( | ||
<> | ||
<h1 className='text-4xl md:text-5xl mr-10'> | ||
{name} | ||
</h1> | ||
<div className='mt-6'> | ||
<div className='flex items-center space-x-2 w-full'> | ||
{gradeStr != null && ( | ||
<RouteGradeChip gradeStr={gradeStr} safety={safety} /> | ||
)} | ||
<RouteTypeChips type={type} /> | ||
</div> | ||
|
||
{length !== -1 && ( | ||
<div className='mt-6 inline-flex items-center justify-left border-2 border-neutral/80 rounded'> | ||
<ArrowsVertical className='h-5 w-5' /> | ||
<span className='bg-neutral/80 text-base-100 px-2 text-sm'>{length}m</span> | ||
</div> | ||
)} | ||
{/* {editMode && <TotalLengthInput />} */} | ||
|
||
<div className='mt-6'> | ||
<div className='text-sm font-medium text-base-content'>{trimLegacyFA(legacyFA)}</div> | ||
</div> | ||
|
||
{(authorMetadata.createdAt != null || authorMetadata.updatedAt != null) && ( | ||
<div className='mt-8 border-t border-b'> | ||
<ArticleLastUpdate {...authorMetadata} /> | ||
</div> | ||
)} | ||
|
||
{/* {!editMode && ( | ||
<div className='mt-8'> | ||
<TickButton climbId={climbId} name={name} grade={yds} /> | ||
</div> | ||
)} */} | ||
</div> | ||
</> | ||
) | ||
} | ||
|
||
const trimLegacyFA = (s: string): string => { | ||
if (s == null || s.trim() === '') return 'FA Unknown' | ||
if (s.startsWith('FA')) return s | ||
return 'FA ' + s | ||
} |
26 changes: 26 additions & 0 deletions
26
src/app/(default)/climb/[[...slug]]/components/ContentBlock.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Climb } from '@/js/types' | ||
|
||
export const ContentBlock: React.FC<Pick<Climb, 'content'>> = ({ content: { description, location, protection } }) => { | ||
return ( | ||
<> | ||
<div className='mb-3 flex justify-between items-center'> | ||
<h3>Description</h3> | ||
</div> | ||
{description} | ||
|
||
{(location?.trim() !== '') && ( | ||
<> | ||
<h3 className='mb-3 mt-6'>Location</h3> | ||
{location} | ||
</> | ||
)} | ||
|
||
{(protection?.trim() !== '') && ( | ||
<> | ||
<h3 className='mb-3 mt-6'>Protection</h3> | ||
{protection} | ||
</> | ||
)} | ||
</> | ||
) | ||
} |
12 changes: 12 additions & 0 deletions
12
src/app/(default)/climb/[[...slug]]/components/PageAlert.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import Link from 'next/link' | ||
import { Bulldozer } from '@phosphor-icons/react/dist/ssr' | ||
|
||
export const PageAlert: React.FC<{ id: string }> = ({ id }) => ( | ||
<div className='alert alert-warning text-md flex justify-center'> | ||
<div className='flex gap-1'> | ||
<Bulldozer size={24} className='mr-2' /> | ||
We're giving this page a facelift. | ||
<Link href={`/climbs/${id}`} className='underline font-semibold'>Visit the previous version</Link> | ||
to make edits. | ||
</div> | ||
</div>) |
29 changes: 29 additions & 0 deletions
29
src/app/(default)/climb/[[...slug]]/components/SiblingClimbs.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { ClimbList } from '@/app/(default)/editArea/[slug]/general/components/climb/ClimbListForm' | ||
import { AreaType } from '@/js/types' | ||
|
||
/** | ||
* Show sibling climbs | ||
*/ | ||
export const SiblingClimbs: React.FC<{ parentArea: AreaType, climbId: string }> = ({ | ||
parentArea, | ||
climbId | ||
}) => { | ||
return ( | ||
<> | ||
<h4> | ||
Routes in{' '} | ||
{parentArea.areaName.includes(', The') | ||
? 'The '.concat(parentArea.areaName.slice(0, -5)) | ||
: parentArea.areaName} | ||
</h4> | ||
<hr className='mt-2 mb-2 border-1 border-base-content' /> | ||
<ClimbList | ||
gradeContext={parentArea.gradeContext} | ||
climbs={parentArea.climbs} | ||
areaMetadata={parentArea.metadata} | ||
routePageId={climbId} | ||
editMode={false} | ||
/> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { notFound, permanentRedirect } from 'next/navigation' | ||
|
||
import { AreaCrumbs } from '@/components/breadcrumbs/AreaCrumbs' | ||
import { DefaultPageContainer } from '../../components/ui/DefaultPageContainer' | ||
import PhotoMontage, { UploadPhotoCTA } from '@/components/media/PhotoMontage' | ||
import { StickyHeaderContainer } from '../../components/ui/StickyHeaderContainer' | ||
import { parseUuidAsFirstParam, climbLeftRightIndexComparator, getFriendlySlug, getClimbPageFriendlyUrl } from '@/js/utils' | ||
import { PageWithCatchAllUuidProps } from '@/js/types/pages' | ||
import { getClimbById } from '@/js/graphql/api' | ||
import { ClimbData } from './components/ClimbData' | ||
import { ContentBlock } from './components/ContentBlock' | ||
import { Summary } from '../../components/ui/Summary' | ||
import { SiblingClimbs } from './components/SiblingClimbs' | ||
import { LazyAreaMap } from '@/components/maps/AreaMap' | ||
import { ClimbType, TagTargetType } from '@/js/types' | ||
import { NeighboringRoutesNav } from '@/components/crag/NeighboringRoute' | ||
import { AreaAndClimbPageActions } from '../../components/AreaAndClimbPageActions' | ||
import { PageAlert } from './components/PageAlert' | ||
/** | ||
* Page cache settings | ||
*/ | ||
export const revalidate = 300 // 5 mins | ||
export const fetchCache = 'force-no-store' // opt out of Nextjs version of 'fetch' | ||
|
||
/** | ||
* Climb page | ||
*/ | ||
export default async function Page ({ params }: PageWithCatchAllUuidProps): Promise<any> { | ||
const climbId = parseUuidAsFirstParam({ params }) | ||
const climb = await getClimbById(climbId) | ||
if (climb == null) { | ||
notFound() | ||
} | ||
|
||
const userProvidedSlug = getFriendlySlug(params.slug?.[1] ?? '') | ||
|
||
const photoList = climb.media | ||
|
||
const { | ||
id, name, type, ancestors, pathTokens, parent | ||
} = climb | ||
|
||
const correctSlug = getFriendlySlug(name) | ||
|
||
if (correctSlug !== userProvidedSlug) { | ||
permanentRedirect(getClimbPageFriendlyUrl(id, name)) | ||
} | ||
|
||
let leftClimb: ClimbType | null = null | ||
let rightClimb: ClimbType | null = null | ||
|
||
const sortedClimbs = [...parent.climbs].sort(climbLeftRightIndexComparator) | ||
|
||
for (const [index, climb] of sortedClimbs.entries()) { | ||
if (climb.id === id) { | ||
leftClimb = (sortedClimbs[index - 1] != null) ? sortedClimbs[index - 1] : null | ||
rightClimb = sortedClimbs[index + 1] != null ? sortedClimbs[index + 1] : null | ||
} | ||
} | ||
|
||
return ( | ||
<DefaultPageContainer | ||
heroAlert={<PageAlert id={id} />} | ||
photoGallery={ | ||
photoList.length === 0 | ||
? <UploadPhotoCTA /> | ||
: <PhotoMontage photoList={photoList} /> | ||
} | ||
pageActions={<AreaAndClimbPageActions name={name} uuid={id} targetType={TagTargetType.climb} />} | ||
breadcrumbs={ | ||
<StickyHeaderContainer> | ||
<AreaCrumbs pathTokens={pathTokens} ancestors={ancestors} /> | ||
</StickyHeaderContainer> | ||
} | ||
leftRightNav={<NeighboringRoutesNav climbs={[leftClimb, rightClimb]} parentArea={parent} />} | ||
summary={{ | ||
left: <ClimbData {...climb} isBoulder={type.bouldering} gradeContext={parent.gradeContext} />, | ||
right: <ContentBlock content={climb.content} /> | ||
}} | ||
map={( | ||
<LazyAreaMap | ||
focused={null} | ||
selected={climb.parent.id} | ||
subAreas={[]} | ||
area={climb.parent} | ||
/>)} | ||
mapContainerClass='block lg:hidden h-[90vh] w-full' | ||
> | ||
<hr className='border-1 my-8' /> | ||
<Summary | ||
columns={{ | ||
left: <SiblingClimbs parentArea={climb.parent} climbId={id} />, | ||
right: ( | ||
<div id='map' className='hidden lg:min-h-[500px] lg:h-full lg:block lg:relative'> | ||
<LazyAreaMap | ||
focused={null} | ||
selected={climb.parent.id} | ||
subAreas={[]} | ||
area={climb.parent} | ||
/> | ||
</div>) | ||
}} | ||
/> | ||
<div className='mt-16' /> | ||
</DefaultPageContainer> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import Link from 'next/link' | ||
import { PencilSimple, MapTrifold } from '@phosphor-icons/react/dist/ssr' | ||
import clz from 'classnames' | ||
|
||
import { SharePageURLButton } from '@/app/(default)/components/SharePageURLButton' | ||
import { UploadPhotoButton } from '@/components/media/PhotoUploadButtons' | ||
import { TagTargetType } from '@/js/types' | ||
|
||
/** | ||
* Main action bar for area & climb page | ||
*/ | ||
export const AreaAndClimbPageActions: React.FC<{ uuid: string, name: string, targetType: TagTargetType }> = ({ uuid, name, targetType }) => { | ||
let url: string | ||
let sharePath: string | ||
let enableEdit = true | ||
let editLabel = 'Edit' | ||
switch (targetType) { | ||
case TagTargetType.area: | ||
url = `/editArea/${uuid}` | ||
sharePath = `/area/${uuid}` | ||
break | ||
case TagTargetType.climb: | ||
url = `/editClimb/${uuid}` | ||
sharePath = `/climb/${uuid}` | ||
enableEdit = false | ||
editLabel = 'Edit (TBD)' | ||
} | ||
return ( | ||
<ul className='flex items-center justify-between gap-2'> | ||
<Link href={url} target='_new' className={clz('btn no-animation shadow-md', enableEdit ? 'btn-solid btn-accent' : 'btn-disabled')}> | ||
<PencilSimple size={20} weight='duotone' /> {editLabel} | ||
</Link> | ||
|
||
<UploadPhotoButton /> | ||
|
||
<Link href='#map' className='btn no-animation'> | ||
<MapTrifold size={20} className='hidden md:inline' /> Map | ||
</Link> | ||
<SharePageURLButton path={sharePath} name={name} /> | ||
</ul> | ||
) | ||
} | ||
|
||
/** | ||
* Skeleton. Height = actual component's button height. | ||
*/ | ||
export const AreaPageActionsSkeleton: React.FC = () => (<div className='w-80 bg-base-200 h-9 rounded-btn' />) |
Oops, something went wrong.