diff --git a/frontend/src/components/Header/BurgerMenu/__tests__/__snapshots__/BurgerMenu.test.tsx.snap b/frontend/src/components/Header/BurgerMenu/__tests__/__snapshots__/BurgerMenu.test.tsx.snap index bd7bd828d..3169c07a8 100644 --- a/frontend/src/components/Header/BurgerMenu/__tests__/__snapshots__/BurgerMenu.test.tsx.snap +++ b/frontend/src/components/Header/BurgerMenu/__tests__/__snapshots__/BurgerMenu.test.tsx.snap @@ -932,13 +932,13 @@ Object { id="accordion__panel-:r1:" > FR EN @@ -1290,13 +1290,13 @@ Object { id="accordion__panel-:r1:" > FR EN diff --git a/frontend/src/components/Link/Link.tsx b/frontend/src/components/Link/Link.tsx index 77a41d8f4..13abe78ab 100644 --- a/frontend/src/components/Link/Link.tsx +++ b/frontend/src/components/Link/Link.tsx @@ -11,7 +11,10 @@ export const Link: React.FC = ({ children, className = '', testId, ...nex return ( {children} diff --git a/frontend/src/components/pages/details/components/DetailsTrekFamilyCarousel/__tests__/__snapshots__/DetailsTrekFamilyCarousel.test.tsx.snap b/frontend/src/components/pages/details/components/DetailsTrekFamilyCarousel/__tests__/__snapshots__/DetailsTrekFamilyCarousel.test.tsx.snap index b268537bd..03c4a161c 100644 --- a/frontend/src/components/pages/details/components/DetailsTrekFamilyCarousel/__tests__/__snapshots__/DetailsTrekFamilyCarousel.test.tsx.snap +++ b/frontend/src/components/pages/details/components/DetailsTrekFamilyCarousel/__tests__/__snapshots__/DetailsTrekFamilyCarousel.test.tsx.snap @@ -33,7 +33,7 @@ Object { tabindex="-1" >
, "container":
= ({ flatPageUrl }) => { - const { flatPage, isLoading, refetch } = useFlatPage(flatPageUrl); + const { flatPage, isLoading, refetch, activitySuggestions } = useFlatPage(flatPageUrl); const intl = useIntl(); + const parsedFlatPage = useMemo(() => { + if (!flatPage?.content || !flatPage.content.length) { + return null; + } + return parse(flatPage.content, { + replace: (domNode: DOMNode) => { + if ( + domNode instanceof Element && + domNode.attribs && + 'data-ids' in domNode.attribs && + 'data-type' in domNode.attribs && + domNode.attribs.class.includes('suggestions') + ) { + const suggestion = activitySuggestions.find( + item => + item.results.map(({ id }) => id).join(',') === + domNode.attribs['data-ids'] && + item.type === getSuggestionType(domNode.attribs['data-type']), + ); + if (!suggestion || suggestion.results.length === 0) { + return null; + } + return ( + + ); + } + return domNode; + }, + }); + }, [activitySuggestions, flatPage?.content] ); + return ( <> = ({ flatPageUrl }) => { ) ) : (
- {flatPage.attachment !== null && flatPage.attachment.length > 0 && ( + {flatPage.attachment && flatPage.attachment.length > 0 && (
= ({ flatPageUrl }) => {
)} -
+
{(flatPage.attachment == null || flatPage.attachment.length === 0) && (

@@ -68,19 +106,19 @@ export const FlatPageUI: React.FC = ({ flatPageUrl }) => {

)} -
- -
+ {flatPage.content !== null && flatPage.content.length > 0 && ( - {parse(flatPage.content)} +
+ {parsedFlatPage} +
)} {flatPage.sources.length > 0 && ( <> diff --git a/frontend/src/components/pages/flatPage/useFlatPage.tsx b/frontend/src/components/pages/flatPage/useFlatPage.tsx index a88bc6249..2e674f22c 100644 --- a/frontend/src/components/pages/flatPage/useFlatPage.tsx +++ b/frontend/src/components/pages/flatPage/useFlatPage.tsx @@ -6,6 +6,9 @@ import { useRouter } from 'next/router'; import { useQuery } from '@tanstack/react-query'; import { ONE_DAY } from 'services/constants/staleTime'; import { useQueryCommonDictionaries } from 'modules/dictionaries/api'; +import { getSuggestionsFromContent } from 'modules/flatpage/utils'; +import { ActivitySuggestion } from 'modules/activitySuggestions/interface'; +import { getActivitySuggestions } from 'modules/activitySuggestions/connector'; export const useFlatPage = (flatPageUrl: string | undefined) => { const language = useRouter().locale ?? getDefaultLanguage(); @@ -14,7 +17,12 @@ export const useFlatPage = (flatPageUrl: string | undefined) => { const commonDictionaries = useQueryCommonDictionaries(language); - const { data, refetch, isLoading, error } = useQuery( + const { + data: flatPage, + refetch, + isLoading, + error, + } = useQuery( ['flatPageDetails', id, language], () => getFlatPageDetails(id, language, commonDictionaries), { @@ -22,5 +30,25 @@ export const useFlatPage = (flatPageUrl: string | undefined) => { staleTime: ONE_DAY, }, ); - return { id, flatPage: data, refetch, isLoading, error, path }; + const suggestions = getSuggestionsFromContent(flatPage?.content ?? ''); + + const activitySuggestionIds = suggestions.flatMap(suggestion => + 'ids' in suggestion ? suggestion.ids : [suggestion.type], + ); + + const { data: activitySuggestions = [] } = useQuery( + ['activitySuggestions', ...activitySuggestionIds, id, language], + () => getActivitySuggestions(suggestions, language, commonDictionaries), + { enabled: suggestions.length > 0 && commonDictionaries !== undefined }, + ); + + return { + id, + flatPage, + activitySuggestions, + refetch, + isLoading, + error, + path, + }; }; diff --git a/frontend/src/components/pages/search/components/ResultCard/ResultCard.tsx b/frontend/src/components/pages/search/components/ResultCard/ResultCard.tsx index f2a822cfa..939b16fa1 100644 --- a/frontend/src/components/pages/search/components/ResultCard/ResultCard.tsx +++ b/frontend/src/components/pages/search/components/ResultCard/ResultCard.tsx @@ -56,7 +56,7 @@ export const ResultCard: React.FC = props => { setHoveredCardId(null); }} className={cn( - 'flex flex-auto flex-col items-stretch border border border-solid border-greySoft hover:border-blackSemiTransparent transition rounded-xl overflow-hidden cursor-pointer', + 'custo-result-card flex flex-auto flex-col items-stretch border border border-solid border-greySoft hover:border-blackSemiTransparent transition rounded-xl overflow-hidden', asColumn !== true && 'desktop:flex-row', className, )} diff --git a/frontend/src/components/pages/search/components/ResultCard/__tests__/__snapshots__/ResultCard.test.tsx.snap b/frontend/src/components/pages/search/components/ResultCard/__tests__/__snapshots__/ResultCard.test.tsx.snap index d09c17516..4d4fba3b9 100644 --- a/frontend/src/components/pages/search/components/ResultCard/__tests__/__snapshots__/ResultCard.test.tsx.snap +++ b/frontend/src/components/pages/search/components/ResultCard/__tests__/__snapshots__/ResultCard.test.tsx.snap @@ -6,7 +6,7 @@ Object { "baseElement":
@@ -219,7 +219,7 @@ Object { , "container":
diff --git a/frontend/src/modules/flatpage/utils.ts b/frontend/src/modules/flatpage/utils.ts new file mode 100644 index 000000000..25d585ec4 --- /dev/null +++ b/frontend/src/modules/flatpage/utils.ts @@ -0,0 +1,42 @@ +import parse, { DOMNode, Element } from 'html-react-parser'; +import { Suggestion } from 'modules/home/interface'; + +export const getSuggestionType = (rawType?: string) => { + switch (rawType) { + case 'touristicContent': + return 'service'; + case 'touristicEvent': + return 'events'; + case 'outdoorSite': + return 'outdoor'; + default: + return 'trek'; + } +}; +export const getSuggestionsFromContent = (rawContent: string) => { + const content = rawContent; + const domNodes: Element[] = []; + parse(content, { + replace: (domNode: DOMNode) => { + const isSuggestionNode = + domNode instanceof Element && + domNode.attribs && + 'data-ids' in domNode.attribs && + 'data-type' in domNode.attribs; + + if (isSuggestionNode) { + domNodes.push(domNode); + } + return domNode; + }, + }); + + const suggestions: Suggestion[] = domNodes.map(domNode => ({ + iconUrl: '', + titleTranslationId: domNode.attribs['data-label'], + ids: domNode.attribs['data-ids'].split(','), + type: getSuggestionType(domNode.attribs['data-type']), + })); + + return suggestions; +}; diff --git a/frontend/src/pages/information/[flatPage].tsx b/frontend/src/pages/information/[flatPage].tsx index f7e64ef9d..047151d27 100644 --- a/frontend/src/pages/information/[flatPage].tsx +++ b/frontend/src/pages/information/[flatPage].tsx @@ -4,9 +4,11 @@ import { FlatPageUI } from 'components/pages/flatPage'; import { dehydrate, QueryClient } from '@tanstack/react-query'; import { routes } from 'services/routes'; import { getCommonDictionaries } from 'modules/dictionaries/connector'; -import { getFlatPageDetails } from '../../modules/flatpage/connector'; -import { isUrlString } from '../../modules/utils/string'; -import { redirectIfWrongUrl } from '../../modules/utils/url'; +import { getActivitySuggestions } from 'modules/activitySuggestions/connector'; +import { getFlatPageDetails } from 'modules/flatpage/connector'; +import { getSuggestionsFromContent } from 'modules/flatpage/utils'; +import { isUrlString } from 'modules/utils/string'; +import { redirectIfWrongUrl } from 'modules/utils/url'; import Custom404 from '../404'; export const getServerSideProps: GetServerSideProps = async context => { @@ -22,6 +24,17 @@ export const getServerSideProps: GetServerSideProps = async context => { const details = await getFlatPageDetails(id, locale, commonDictionaries); await queryClient.prefetchQuery(['flatPageDetails', id, locale], () => details); + const suggestions = getSuggestionsFromContent(details.content); + + const activitySuggestionIds = suggestions.flatMap(suggestion => + 'ids' in suggestion ? suggestion.ids : [suggestion.type], + ); + + await queryClient.prefetchQuery( + ['activitySuggestions', ...activitySuggestionIds, id, locale], + () => getActivitySuggestions(suggestions, locale, commonDictionaries), + ); + const redirect = redirectIfWrongUrl( id, details.title, diff --git a/frontend/src/public/style.css b/frontend/src/public/style.css index 9afd23af3..24811105a 100644 --- a/frontend/src/public/style.css +++ b/frontend/src/public/style.css @@ -13,7 +13,9 @@ width: 10px; height: 10px; border: 2px solid transparent; - box-shadow: 0 0 0 2px, inset 0 0 0 10px; + box-shadow: + 0 0 0 2px, + inset 0 0 0 10px; border-radius: 100px; } .gg-track::after, @@ -43,7 +45,11 @@ .elevation-div { height: 25%; - font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif; + font: + 12px/1.5 'Helvetica Neue', + Arial, + Helvetica, + sans-serif; } .lightblue-theme.elevation-control.elevation .background { @@ -86,3 +92,89 @@ details > summary.list-none::-webkit-details-marker, details > summary.list-none::marker { display: none; } + +/* Flatpage WYSIWYG */ +.custo-page-WYSIWYG a:not(.custo-suggestions a, .button-link) { + @apply text-primary1 transition-colors hover:text-primary1-light focus:text-primary1-light; +} + +.custo-page-WYSIWYG h1:not(.custo-suggestions h1) { + @apply text-H2 font-bold desktop:text-H1; +} + +.custo-page-WYSIWYG h2:not(.custo-suggestions h2) { + @apply text-H3 font-bold desktop:text-H2; +} + +.custo-page-WYSIWYG h3:not(.custo-suggestions h3), +.custo-page-WYSIWYG h4:not(.custo-suggestions h4) { + @apply text-H4 font-bold desktop:text-H3; +} + +.custo-page-WYSIWYG p:not(.custo-suggestions p) { + @apply mb-4 desktop:mb-6; +} + +.custo-page-WYSIWYG h1:not(.custo-suggestions h1), +.custo-page-WYSIWYG h2:not(.custo-suggestions h2), +.custo-page-WYSIWYG h3:not(.custo-suggestions h3) { + @apply clear-both; +} + +.custo-page-WYSIWYG h1:not(.custo-suggestions h1), +.custo-page-WYSIWYG h2:not(.custo-suggestions h2), +.custo-page-WYSIWYG h3:not(.custo-suggestions h3), +.custo-page-WYSIWYG h4:not(.custo-suggestions h4), +.custo-page-WYSIWYG h5:not(.custo-suggestions h5) { + @apply my-4 desktop:my-10; +} + +.custo-page-WYSIWYG h6:not(.custo-suggestions h6) { + @apply my-4 desktop:my-8; +} + +.custo-page-WYSIWYG img:not(.custo-suggestions img) { + @apply w-auto my-auto mx-1; +} + +.custo-page-WYSIWYG iframe:not(.custo-suggestions iframe) { + @apply my-8 w-full h-auto desktop:my-12; +} + +.custo-page-WYSIWYG b:not(.custo-suggestions b), +.custo-page-WYSIWYG strong:not(.custo-suggestions strong) { + @apply font-bold; +} + +.custo-page-WYSIWYG em:not(.custo-suggestions em) { + @apply text-P2 italic desktop:text-P1; +} + +.custo-page-WYSIWYG ul:not(.custo-suggestions ul) { + @apply my-4; +} + +.custo-page-WYSIWYG ul:not(.custo-suggestions ul) > li { + @apply pl-4 list-inside list-disc desktop:mb-2; +} + +.custo-page-WYSIWYG .custo-suggestions { + @apply clear-both mt-6 desktop:mt-10 w-full; +} + +.custo-page-WYSIWYG .information { + @apply clear-both py-4 px-10 rounded-lg bg-greySoft; +} + +.custo-page-WYSIWYG .align-left { + @apply mb-8 desktop:float-start desktop:mr-12 desktop:mb-12; +} + +.custo-page-WYSIWYG .align-right { + @apply mb-8 desktop:float-end desktop:ml-12 desktop:mb-12; +} + +.custo-page-WYSIWYG .button-link { + @apply py-3 px-4 border border-solid border-primary1 text-primary1 bg-white font-semibold transition-colors rounded-lg; + @apply hover:bg-primary2 focus:bg-primary2; +}