Skip to content

Commit

Permalink
Merge pull request #38 from Vizzuality/cms/preview
Browse files Browse the repository at this point in the history
Add story preview
  • Loading branch information
barbara-chaves authored Dec 19, 2023
2 parents f2759b8 + dc5226c commit f63bc6a
Show file tree
Hide file tree
Showing 25 changed files with 1,662 additions and 3,600 deletions.
37 changes: 37 additions & 0 deletions client/src/app/api/preview/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// route handler with secret and slug
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';

import env from '@/env.mjs';

import { getStoriesId } from '@/types/generated/story';

export async function GET(request: Request) {
// Parse query string parameters
const { searchParams } = new URL(request.url);
const secret = searchParams.get('secret');
const slug = searchParams.get('slug');

// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== env.NEXT_PUBLIC_PREVIEW_SECRET || !slug) {
return new Response('Invalid token', { status: 401 });
}

// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS

const story = await getStoriesId(+slug);

// If the slug doesn't exist prevent draft mode from being enabled
if (!story) {
return new Response('Invalid slug', { status: 401 });
}

// Enable Draft Mode by setting the cookie
draftMode().enable();

// Redirect to the path from the fetched story
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(`${env.NEXT_PUBLIC_URL}/stories/${slug}`);
}
8 changes: 3 additions & 5 deletions client/src/containers/map/markers/story-markers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { stepAtom } from '@/store/stories';
import { useGetStoriesId } from '@/types/generated/story';

import StoryMarkerMedia from './marker';
import { StoryStepMap } from '@/types/story';
// import Carousel from './carousel';
// import { Dialog, DialogContent } from '@/components/ui/dialog';

Expand All @@ -33,12 +34,9 @@ const StoryMarkers = () => {
populate: 'deep',
});
// const [currentMedia, setCurrentMedia] = useState<number>();

const markers: StoryMarker[] = useMemo(() => {
return (
storyData?.data?.attributes?.steps?.data?.[step]?.attributes?.layout[0].map?.markers || []
);
}, [step, storyData?.data?.attributes?.steps?.data]);
return (storyData?.data?.attributes?.steps?.[step]?.map as StoryStepMap)?.markers || [];
}, [step, storyData?.data?.attributes?.steps]);

// const medias = useMemo(() => {
// return markers?.map((marker) => ({
Expand Down
38 changes: 14 additions & 24 deletions client/src/containers/story/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,13 @@ import { layersAtom, tmpBboxAtom } from '@/store';
import { stepAtom } from '@/store/stories';

import { useGetStoriesId } from '@/types/generated/story';
import { Bbox } from '@/types/map';

import { Button } from '@/components/ui/button';

import Step from './steps';
import { ScrollItemController } from './steps/controller/controller-item';
import { ScrollItem } from './steps/controller/scroll-item';

type StepLocation = {
bbox: Bbox;
zoom: number;
pitch: number;
bearing: number;
padding: {
top: number;
left: number;
right: number;
bottom: number;
};
latitude: number;
longitude: number;
};
import { isMapNotEmpty } from './utils';

const headerButtonClassName =
'rounded-4xl h-auto border-gray-800 bg-[hsl(198,100%,14%)]/75 px-5 py-2.5 hover:bg-gray-800';
Expand All @@ -54,7 +39,7 @@ const Story = () => {
});

const story = useMemo(() => storyData?.data?.attributes, [storyData]);
const steps = useMemo(() => story?.steps?.data || [], [story]);
const steps = useMemo(() => story?.steps || [], [story]);

const handleGoHome = () => {
resetLayers();
Expand All @@ -63,23 +48,28 @@ const Story = () => {

useEffect(() => {
if (!steps) return;
const stepLayout = steps[step]?.attributes?.layout?.[0];
const currStep = steps[step];

// Location
const stepLocation = stepLayout?.map?.location;
if (!currStep || !isMapNotEmpty(currStep.map)) {
return;
}

// Bbox
const stepLocation = currStep?.map.location;
if (stepLocation) {
const { bbox, ...options } = stepLocation as StepLocation;
const { bbox, ...options } = stepLocation;
setTmpBbox({
bbox,
options,
});
}

// Layers
const stepLayers = stepLayout?.layers;
const stepLayers = currStep.layers;
if (stepLayers) {
const _layers: number[] =
stepLayers.data?.reduce(
(acc: number[], layer: { id: number }) => (layer.id ? [...acc, layer.id] : acc),
(acc: number[], layer) => (layer.id ? [...acc, layer.id] : acc),
[]
) || [];
setLayers(_layers);
Expand Down Expand Up @@ -116,7 +106,7 @@ const Story = () => {
)}
key={index}
newStep={index}
title={s.attributes?.layout[0]?.card && s.attributes?.layout[0]?.card[0]?.title}
title=""
/>
))}
</div>
Expand Down
26 changes: 8 additions & 18 deletions client/src/containers/story/steps/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@ import { cn } from '@/lib/classnames';
import { stepAtom } from '@/store/stories';

import {
StepLayoutMediaStepComponentMedia,
StepLayoutOutroStepComponentMedia,
StoryCategory,
StoryStepsDataItem,
StoryStepsItem,
} from '@/types/generated/strapi.schemas';

import MapStepLayout from './layouts/map-step';
import MediaStepLayout from './layouts/media-step';
import OutroStepLayout from './layouts/outro-step';
import { getStepType } from './utils';

type StepProps = PropsWithChildren<{
media?: StepLayoutMediaStepComponentMedia | StepLayoutOutroStepComponentMedia;
step: StoryStepsDataItem;
media?: StepLayoutOutroStepComponentMedia;
step: StoryStepsItem;
category?: StoryCategory;
index: number;
}>;
Expand All @@ -31,7 +29,7 @@ const Step = ({ step, category, index }: StepProps) => {
const type = getStepType(step);

const STEP_COMPONENT = useMemo(() => {
const stepLayout = step?.attributes?.layout?.[0];
const stepLayout = step;
if (!type || !stepLayout) return null;

switch (type) {
Expand All @@ -40,32 +38,24 @@ const Step = ({ step, category, index }: StepProps) => {
<MapStepLayout
stepIndex={index}
category={category?.data?.attributes}
step={stepLayout}
step={step}
showContent={currentStep === index}
/>
);
}
case 'media-step':
return <MediaStepLayout step={stepLayout} />;

case 'outro-step':
return (
<OutroStepLayout
step={stepLayout}
step={step}
showContent={currentStep === index}
categoryId={category?.data?.id}
/>
);
default:
return null;
}
}, [
step?.attributes?.layout,
type,
currentStep,
index,
category?.data?.id,
category?.data?.attributes,
]);
}, [step, type, currentStep, index, category?.data?.id, category?.data?.attributes]);

return (
<div className="pointer-events-none h-screen w-full ">
Expand Down
52 changes: 28 additions & 24 deletions client/src/containers/story/steps/layouts/map-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useScrollToItem } from '@/lib/scroll';

import {
StepLayoutMapStepComponent,
StepLayoutItem,
StoryStepsItem,
StoryCategoryDataAttributes,
} from '@/types/generated/strapi.schemas';

Expand All @@ -21,7 +21,7 @@ const Legend = dynamic(() => import('@/containers/map/legend'), {
});

type MapStepLayoutProps = {
step: StepLayoutItem;
step: StoryStepsItem;
category: StoryCategoryDataAttributes | undefined;
stepIndex: number;
showContent?: boolean;
Expand Down Expand Up @@ -78,30 +78,34 @@ const MapStepLayout = ({ step, category, showContent, stepIndex }: MapStepLayout
</div>
<div className="">
<div className="flex h-fit min-h-full flex-col items-end justify-center space-y-6 pb-6">
{card?.map((item) => (
<div
key={item?.id}
onClick={handleClickCard}
className={cn(
'pointer-events-auto cursor-pointer overflow-hidden rounded border border-gray-800 bg-[#335e6f] bg-opacity-50 p-8 backdrop-blur transition-all duration-300 ease-in-out',
showContent ? 'opacity-100' : 'opacity-0'
)}
>
<div className="w-[400px] space-y-1">
{item?.title && <h2 className="font-notes text-2xl font-bold">{item?.title}</h2>}
{!!item?.content && (
<div className="font-open-sans space-y-4">
{item.content.split('\n').map((p, i) => (
<p key={i} className="text-sm">
{p}
</p>
))}
</div>
{card?.map((item) => {
return (
<div
key={item?.id}
onClick={handleClickCard}
className={cn(
'pointer-events-auto cursor-pointer overflow-hidden rounded border border-gray-800 bg-[#335e6f] bg-opacity-50 p-8 backdrop-blur transition-all duration-300 ease-in-out',
showContent ? 'opacity-100' : 'opacity-0'
)}
{!!item?.widget && <Chart options={item?.widget} />}
>
<div className="w-[400px] space-y-1">
{item?.title && (
<h2 className="font-notes text-2xl font-bold">{item?.title}</h2>
)}
{!!item?.content && (
<div className="font-open-sans space-y-4">
{item.content.split('\n').map((p, i) => (
<p key={i} className="text-sm">
{p}
</p>
))}
</div>
)}
{!!item?.widget && <Chart options={item?.widget} />}
</div>
</div>
</div>
))}
);
})}
</div>
</div>
</div>
Expand Down
20 changes: 0 additions & 20 deletions client/src/containers/story/steps/layouts/media-step.tsx

This file was deleted.

16 changes: 8 additions & 8 deletions client/src/containers/story/steps/layouts/outro-step.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import Image from 'next/image';
import { useRouter } from 'next/navigation';
Expand Down Expand Up @@ -80,13 +80,13 @@ const OutroStepLayout = ({ step, showContent }: MediaStepLayoutProps) => {

const isVideo = mediaType.includes('video');

const handlePlayVideo = useCallback(
(e: React.MouseEvent<HTMLVideoElement, MouseEvent>, action: 'play' | 'pause') => {
if (action === 'play') e.currentTarget.play();
else e.currentTarget.pause();
},
[]
);
// const handlePlayVideo = useCallback(
// (e: React.MouseEvent<HTMLVideoElement, MouseEvent>, action: 'play' | 'pause') => {
// if (action === 'play') e.currentTarget.play();
// else e.currentTarget.pause();
// },
// []
// );

const scale = useTransform(scrollYProgress, [0.5, 0.7], ['1', '2']);
const scaleContent = useTransform(scrollYProgress, [0.5, 0.7], ['1', '0.75']);
Expand Down
11 changes: 4 additions & 7 deletions client/src/containers/story/steps/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import {
StepLayoutMediaStepComponentMedia,
StepLayoutOutroStepComponentMedia,
StepListResponseDataItem,
StoryStepsItem,
} from '@/types/generated/strapi.schemas';

export const getStepType = (step: StepListResponseDataItem) => {
return step?.attributes?.layout?.[0]?.__component?.split('.')?.[1];
export const getStepType = (step: StoryStepsItem) => {
return step?.__component?.split('.')?.[1];
};

export const getMedia = (
media?: StepLayoutMediaStepComponentMedia | StepLayoutOutroStepComponentMedia
) => {
export const getMedia = (media?: StepLayoutOutroStepComponentMedia) => {
const url = `${process.env.NEXT_PUBLIC_API_URL?.replace('/api', '')}${
media?.data?.attributes?.url
}`;
Expand Down
10 changes: 10 additions & 0 deletions client/src/containers/story/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StepLayoutMapStepComponent, StoryStepsItem } from '@/types/generated/strapi.schemas';
import { StoryStepMap } from '@/types/story';

export const isMapStep = (step: StoryStepsItem): step is StepLayoutMapStepComponent => {
return !!step?.__component?.includes('map-step');
};

export const isMapNotEmpty = (map: StoryStepsItem['map']): map is StoryStepMap => {
return Object.values((map as StoryStepMap)?.location).length > 0;
};
2 changes: 2 additions & 0 deletions client/src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const env = createEnv({
NEXT_PUBLIC_MAPBOX_API_TOKEN: z.string(),
NEXT_PUBLIC_GA_TRACKING_ID: z.string().optional(),
NEXT_PUBLIC_BASE_PATH: z.string().optional(),
NEXT_PUBLIC_PREVIEW_SECRET: z.string().optional(),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
Expand All @@ -46,6 +47,7 @@ export const env = createEnv({
RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED:
process.env.RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED,
NEXT_PUBLIC_BASE_PATH: process.env.NEXT_PUBLIC_BASE_PATH,
NEXT_PUBLIC_PREVIEW_SECRET: process.env.NEXT_PUBLIC_PREVIEW_SECRET,
},
});

Expand Down
Loading

0 comments on commit f63bc6a

Please sign in to comment.