Skip to content
Open
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
26 changes: 22 additions & 4 deletions src/app/[sanitySectionSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import compact from 'lodash/compact';

import type { Metadata } from 'next';

import LandingPage from '@/components/LandingPage';
import { DEFAULT_SECTION } from '@/components/LandingPage/constants';
import { getSection } from '@/components/LandingPage/utils';

import { generateMetadataFromSanity } from '@/components/LandingPage/metadata';
import { DEFAULT_SECTION, SECTIONS } from '@/components/LandingPage/constants';
import { fetchSanityPageContent } from '@/services/sanity/sanity';
import { getSection } from '@/components/LandingPage/utils';

export type ParamProps = {
params: Promise<{ sanitySectionSlug: string }>;
Expand All @@ -16,17 +20,31 @@ export async function generateMetadata(props: ParamProps): Promise<Metadata> {
return metadata;
}

export async function generateStaticParams() {
const paths = compact(
SECTIONS.map((section) => ({
sanitySectionSlug: section.slug.split('/').pop(),
}))
);
return paths;
}

export const dynamicParams = true;
export const dynamic = 'force-static';

export default async function SanityContentPage({
params: promisedParams,
}: {
params: Promise<{ sanitySectionSlug: string }>;
}) {
const params = await promisedParams;

const sanitySection = getSection(params.sanitySectionSlug);

if (sanitySection.slug === DEFAULT_SECTION.slug) {
notFound();
}

return <LandingPage section={sanitySection.index} />;
const content = await fetchSanityPageContent(sanitySection);
return <LandingPage section={sanitySection.index} content={content} />;
}
9 changes: 8 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Metadata } from 'next';

import LandingPage from '@/components/LandingPage';

import { generateMetadataFromSanity } from '@/components/LandingPage/metadata';
import { EnumSection } from '@/components/LandingPage/sections/sections';
import { DEFAULT_SECTION } from '@/components/LandingPage/constants';
import { fetchSanityPageContent } from '@/services/sanity';

export async function generateMetadata(): Promise<Metadata> {
const metadata = await generateMetadataFromSanity('/');
Expand All @@ -15,5 +18,9 @@ export default async function RootPage({
searchParams: Promise<{ errorcode: string | undefined }>;
}) {
const searchParams = await promisedParams;
return <LandingPage section={EnumSection.Home} errorCode={searchParams.errorcode} />;
const content = await fetchSanityPageContent(DEFAULT_SECTION);

return (
<LandingPage content={content} section={EnumSection.Home} errorCode={searchParams.errorcode} />
);
}
42 changes: 31 additions & 11 deletions src/components/LandingPage/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import VerticalSpace from './components/VerticalSpace';
import FooterPanel from './layout/FooterPanel';
import Hero from './layout/Hero';
import Menu from './layout/Menu';
import SectionContact from './sections/SectionContact';
import SectionGeneric from './sections/SectionGeneric';
import SectionNews from './sections/SectionNews';
import { EnumSection } from './sections/sections';
import { ContentForRichText } from './content';

import AcceptInviteErrorDialog from '@/components/Invites/AcceptInviteErrorDialog';
import { logError } from '@/util/logger';
Expand All @@ -21,13 +21,21 @@ import { classNames } from '@/util/utils';
import styles from './LandingPage.module.css';
import './global.css';

interface LandingPageProps {
className?: string;
section: EnumSection;
errorCode?: string;
}
type LandingPageProps =
| {
className?: string;
errorCode?: string;
section: EnumSection;
content: ContentForRichText;
}
| {
className?: string;
errorCode?: string;
section: EnumSection.News;
content?: never;
};

export default function LandingPage({ className, section, errorCode }: LandingPageProps) {
export default function LandingPage({ className, section, errorCode, content }: LandingPageProps) {
const scrollHasStarted = useScrollHasStarted();

useEffect(() => {
Expand All @@ -42,16 +50,29 @@ export default function LandingPage({ className, section, errorCode }: LandingPa
<div className={classNames(className, styles.landingPage)}>
<Menu scrollHasStarted={scrollHasStarted} section={section} />
<Hero section={section} />
<PaddedBlock>{renderSection(section)}</PaddedBlock>
<PaddedBlock>
{section === EnumSection.News
? renderSection({ section })
: renderSection({ section, content })}
</PaddedBlock>
<VerticalSpace height="30px" />
<FooterPanel />
{errorCode && <AcceptInviteErrorDialog errorCode={errorCode} />}
</div>
</>
);
}
type RenderSectionProps =
| {
section: EnumSection;
content: ContentForRichText;
}
| {
section: EnumSection.News;
content?: never;
};

function renderSection(section: EnumSection): React.ReactNode {
function renderSection({ section, content }: RenderSectionProps): React.ReactNode {
switch (section) {
case EnumSection.Home:
case EnumSection.About:
Expand All @@ -64,9 +85,8 @@ function renderSection(section: EnumSection): React.ReactNode {
case EnumSection.PrivacyPolicy:
case EnumSection.ComingSoon:
case EnumSection.Story:
return <SectionGeneric section={section} />;
case EnumSection.Contact:
return <SectionContact />;
return <SectionGeneric content={content} />;
case EnumSection.News:
return <SectionNews />;
default:
Expand Down
38 changes: 38 additions & 0 deletions src/components/LandingPage/client-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import React from 'react';
import { getSection, sanitizeURL } from './utils';
import { ID_MENU } from './constants';
import { EnumSection } from './sections/sections';
import { isBrowser } from '@/utils/environment';

export function gotoSection(slugOrIndex: string | EnumSection) {
const section = getSection(slugOrIndex);
const url = sanitizeURL(section.slug);
window.location.href = url;
}

function useResizeObserver(callback: ResizeObserverCallback): ResizeObserver | null {
const ref = React.useRef<ResizeObserver | null>(null);
if (isBrowser()) {
if (!ref.current) ref.current = new ResizeObserver(callback);
return ref.current;
}
return null;
}

export function useMenuHeight(): number {
const [menuHeight, setMenuHeight] = React.useState(0);
const handleResize = React.useCallback(() => {
const menu = document.getElementById(ID_MENU);
if (!menu) return;

setMenuHeight(menu.clientHeight);
}, [setMenuHeight]);
const observer = useResizeObserver(handleResize);
React.useEffect(() => {
observer?.observe(document.body);
return () => observer?.unobserve(document.body);
}, [observer]);
return menuHeight;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ export default function ProgressiveImage({
}}
className={classNames(className, styles.progressiveImage)}
>
<Image
className={classNames(styles.image, loaded && styles.show)}
onLoad={() => {
setLoaded(true);
}}
src={src}
width={width}
height={height}
alt={alt}
/>
{src && (
<Image
className={classNames(styles.image, loaded && styles.show)}
onLoad={() => {
setLoaded(true);
}}
src={src}
width={width}
height={height}
alt={alt}
/>
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import VerticalSpace from '../../VerticalSpace';

import { ContentForRichTextPreview } from '@/components/LandingPage/content';
import { styleBlockFullWidth, styleBlockSmall, styleLayout } from '@/components/LandingPage/styles';
import { gotoSection } from '@/components/LandingPage/utils';
import { gotoSection } from '@/components/LandingPage/client-utils';
import { classNames } from '@/util/utils';

import styles from './SanityContentPreview.module.css';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import React from 'react';

import {
Expand Down
33 changes: 19 additions & 14 deletions src/components/LandingPage/components/Video/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use client';

/* eslint-disable jsx-a11y/media-has-caption */
import { usePathname } from 'next/navigation';

import React, { CSSProperties, SyntheticEvent, useRef, useState } from 'react';

import { isNumber } from '@/util/type-guards';
Expand Down Expand Up @@ -117,20 +120,22 @@ export default function ProgressiveVideo({
)}
</>
)}
<video
className={classNames(controls && styles.pointer)}
src={src}
ref={refVideo}
controls={controls}
muted
loop={!controls}
disablePictureInPicture
playsInline
autoPlay={!controls && !isHomepage}
onPlay={handlePlay}
onCanPlay={handleReady}
onTimeUpdate={handleTimeUpdate}
/>
{src && (
<video
className={classNames(controls && styles.pointer)}
src={src}
ref={refVideo}
controls={controls}
muted
loop={!controls}
disablePictureInPicture
playsInline
autoPlay={!controls && !isHomepage}
onPlay={handlePlay}
onCanPlay={handleReady}
onTimeUpdate={handleTimeUpdate}
/>
)}
</div>
);
}
9 changes: 4 additions & 5 deletions src/components/LandingPage/content/_common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { PortableTextBlock } from '@portabletext/react';

import { typeImage } from './shared-types';
import { assertType, TypeDef } from '@/util/type-guards';

/**
Expand All @@ -18,8 +20,5 @@ export function tryType(typeName: string, data: unknown, type: TypeDef): boolean

export type RichText = PortableTextBlock | PortableTextBlock[];

export const typeImage: Record<string, TypeDef> = {
imageURL: 'string',
imageWidth: 'number',
imageHeight: 'number',
};
// Re-export shared typeImage
export { typeImage };
11 changes: 11 additions & 0 deletions src/components/LandingPage/content/shared-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { TypeDef } from '@/util/type-guards';

// Shared type definitions that can be used on both server and client side
export const typeStringOrNull: TypeDef = ['|', 'string', 'null', 'undefined'];
export const typeNumberOrNull: TypeDef = ['|', 'number', 'null', 'undefined'];
export const typeBooleanOrNull: TypeDef = ['|', 'boolean', 'null', 'undefined'];
export const typeImage = {
imageURL: 'string',
imageWidth: 'number',
imageHeight: 'number',
} satisfies TypeDef;
13 changes: 5 additions & 8 deletions src/components/LandingPage/content/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
'use client';

import { RichText, tryType } from './_common';
import { typeStringOrNull, typeNumberOrNull, typeBooleanOrNull, typeImage } from './shared-types';

import { TypeDef } from '@/util/type-guards';

export const typeStringOrNull: TypeDef = ['|', 'string', 'null', 'undefined'];
export const typeNumberOrNull: TypeDef = ['|', 'number', 'null', 'undefined'];
export const typeBooleanOrNull: TypeDef = ['|', 'boolean', 'null', 'undefined'];
export const typeImage = {
imageURL: 'string',
imageWidth: 'number',
imageHeight: 'number',
} satisfies TypeDef;
// Re-export shared types for backward compatibility
export { typeStringOrNull, typeNumberOrNull, typeBooleanOrNull, typeImage };

export interface ContentForRichTextImage {
_type: 'imageBlock';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SocialMediaLinks from '../../components/social-media-links';
import { MENU_ITEMS } from '../../constants';
import { EnumSection } from '../../sections/sections';
import { gotoSection } from '../../utils';
import { gotoSection } from '../../client-utils';
import NewsLetterSubscription from './NewsLetterSubscription';

import { classNames } from '@/util/utils';
Expand Down
5 changes: 4 additions & 1 deletion src/components/LandingPage/metadata/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use server';

import { Metadata } from 'next';

import { tryType, typeStringOrNull } from '../content';
import { tryType } from '../content';
import { typeStringOrNull } from '../content/shared-types';
import queryTemplate from './metadata.groq';
import { DEFAULT_METADATA } from './default';
import { fetchSanity } from '@/services/sanity';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import React from 'react';

import SectionGeneric from '../SectionGeneric';
import { EnumSection } from '../sections';
import { ContentForRichText } from '../../content';

export default function SectionContact() {
return (
<>
<SectionGeneric section={EnumSection.Contact} />
</>
);
export default function SectionContact({ content }: { content: ContentForRichText }) {
return <SectionGeneric content={content} />;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
'use client';

import React from 'react';
import isEmpty from 'lodash/isEmpty';

import SanityContentRTF from '../../components/SanityContentRTF';
import { useSanityContentRTF } from '../../content/content';
import { EnumSection } from '../sections';
import { ContentForRichText } from '../../content';

interface GenericSectionProps {
section: EnumSection;
content: ContentForRichText;
}

export default function SectionGeneric({ section }: GenericSectionProps) {
const content = useSanityContentRTF(section);

export default function SectionGeneric({ content }: GenericSectionProps) {
if (isEmpty(content)) return null;
return <SanityContentRTF value={content} />;
}
Loading