diff --git a/web/lib/queries/magazine.ts b/web/lib/queries/magazine.ts index 3ae634c01..00872cd02 100644 --- a/web/lib/queries/magazine.ts +++ b/web/lib/queries/magazine.ts @@ -95,11 +95,11 @@ export const allMagazineDocuments = /* groq */ ` //&& (${publishDateTimeQuery} < $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id < $lastId)) const prevDirectionFilter = /* groq */ ` -&& (${publishDateTimeQuery} < $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id < $lastId)) +&& (${publishDateTimeQuery} > $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id > $lastId)) ` //&& (${publishDateTimeQuery} > $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id > $lastId)) const nextDirectionFilter = /* groq */ ` -&& (${publishDateTimeQuery} > $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id > $lastId)) +&& (${publishDateTimeQuery} < $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id < $lastId)) ` export const getMagazineArticlesByTag = (hasFirstId = false, hasLastId = false) => /* groq */ ` diff --git a/web/lib/queries/newsroom.ts b/web/lib/queries/newsroom.ts index bad5704af..5709b7150 100644 --- a/web/lib/queries/newsroom.ts +++ b/web/lib/queries/newsroom.ts @@ -1,7 +1,10 @@ import linkSelectorFields, { linkReferenceFields } from './common/actions/linkSelectorFields' import markDefs from './common/blockEditorMarks' -import { sameLang } from './common/langAndDrafts' +import { noDrafts, sameLang } from './common/langAndDrafts' +import { ingressForNewsQuery } from './common/newsSubqueries' +import { publishDateTimeQuery } from './common/publishDateTime' import { seoAndSomeFields } from './common/seoAndSomeFields' +import slugsForNewsAndMagazine from './slugsForNewsAndMagazine' export const newsroomQuery = /* groq */ ` *[_type == "newsroom" && ${sameLang}] { @@ -22,3 +25,55 @@ export const newsroomQuery = /* groq */ ` backgroundImage, "fallbackImages": imageThumbnailFallbacks[]{...} }` + +const prevDirectionFilter = /* groq */ ` +&& (${publishDateTimeQuery} > $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id > $lastId)) +` + +const nextDirectionFilter = /* groq */ ` +&& (${publishDateTimeQuery} < $lastPublishedAt || (${publishDateTimeQuery} == $lastPublishedAt && _id < $lastId)) +` + +export const allNewsDocuments = /* groq */ ` +*[_type == "news" && ${sameLang} && ${noDrafts} ] | order(${publishDateTimeQuery} desc)[0...30] { +"id": _id, +"updatedAt": _updatedAt, +title, +heroImage, +"slug": slug.current, +"tags":tags[]->{ + "key": key.current, + "label":title[$lang], +}, +"countryTags":countryTags[]->{ + "key": key.current, + "label":title[$lang], +}, +"publishDateTime": ${publishDateTimeQuery}, +${slugsForNewsAndMagazine}, +${ingressForNewsQuery}, +}` + +export const getNewsArticlesByPage = (hasFirstId = false, hasLastId = false) => /* groq */ ` +*[_type == 'news' && ${sameLang} && ${noDrafts} + ${hasLastId ? nextDirectionFilter : ''} + ${hasFirstId ? prevDirectionFilter : ''} + ] | order(${publishDateTimeQuery} desc)[0...30]{ +"id": _id, +"updatedAt": _updatedAt, +"slug": slug.current, +title, +heroImage, +"tags":tags[]->{ + "key": key.current, + "label":title[$lang], +}, +"countryTags":countryTags[]->{ + "key": key.current, + "label":title[$lang], +}, +"publishDateTime": ${publishDateTimeQuery}, +${slugsForNewsAndMagazine}, +${ingressForNewsQuery}, + } +` diff --git a/web/pages/api/news/next.ts b/web/pages/api/news/next.ts new file mode 100644 index 000000000..42c944b94 --- /dev/null +++ b/web/pages/api/news/next.ts @@ -0,0 +1,21 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import { getNameFromLocale } from '../../../lib/localization' +import { sanityClient } from '../../../lib/sanity.server' +import { getNewsArticlesByPage } from '../../../lib/queries/newsroom' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const lang = req.query.lang === 'no' ? getNameFromLocale('no') : getNameFromLocale('en') // Defaults to 'en' if the lang parameter is not 'no' + + console.log('get next from req.query.lastId', req.query.lastId) + console.log('get next from req.query.lastPublishedAt', req.query.lastPublishedAt) + try { + const news = await sanityClient.fetch(getNewsArticlesByPage(false, true), { + lang, + lastId: req.query.lastId, + lastPublishedAt: req.query.lastPublishedAt, + }) + res.status(200).json({ news: news }) + } catch (err) { + res.status(500).json({ error: 'Failed to fetch news' }) + } +} diff --git a/web/pages/api/news/prev.ts b/web/pages/api/news/prev.ts new file mode 100644 index 000000000..c0d2e54dd --- /dev/null +++ b/web/pages/api/news/prev.ts @@ -0,0 +1,19 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import { getNameFromLocale } from '../../../lib/localization' +import { sanityClient } from '../../../lib/sanity.server' +import { getNewsArticlesByPage } from '../../../lib/queries/newsroom' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const lang = req.query.lang === 'no' ? getNameFromLocale('no') : getNameFromLocale('en') // Defaults to 'en' if the lang parameter is not 'no' + + try { + const news = await sanityClient.fetch(getNewsArticlesByPage(true, false), { + lang, + lastId: req.query.lastId, + lastPublishedAt: req.query.lastPublishedAt, + }) + res.status(200).json({ news: news }) + } catch (err) { + res.status(500).json({ error: 'Failed to fetch news' }) + } +} diff --git a/web/pages/news/index.global.tsx b/web/pages/news/index.global.tsx index 0bf143c34..49949f9fd 100644 --- a/web/pages/news/index.global.tsx +++ b/web/pages/news/index.global.tsx @@ -1,39 +1,29 @@ import { GetServerSideProps } from 'next' -import { InstantSearchSSRProvider, getServerState } from 'react-instantsearch' import type { AppProps } from 'next/app' import { IntlProvider } from 'react-intl' import Footer from '../../pageComponents/shared/Footer' import Header from '../../pageComponents/shared/Header' -import { newsroomQuery } from '../../lib/queries/newsroom' +import { allNewsDocuments, newsroomQuery } from '../../lib/queries/newsroom' import getIntl from '../../common/helpers/getIntl' import { getNameFromLocale, getIsoFromLocale } from '../../lib/localization' import { defaultLanguage } from '../../languages' import { AlgoliaIndexPageType, NewsRoomPageType } from '../../types' -import { getComponentsData } from '../../lib/fetchData' -import { renderToString } from 'react-dom/server' -import NewsRoomTemplate from '@templates/newsroom/Newsroom' +import { getComponentsData, getData } from '../../lib/fetchData' +import NewsRoomTemplateSanity from '@templates/newsroom/sanity/NewsroomSanity' -export default function NewsRoom({ isServerRendered = false, serverState, data, url }: AlgoliaIndexPageType) { +export default function NewsRoom({ data }: AlgoliaIndexPageType) { const defaultLocale = defaultLanguage.locale const { pageData, slug, intl } = data const locale = data?.intl?.locale || defaultLocale return ( - - - - - + + + ) } @@ -97,28 +87,22 @@ export const getServerSideProps: GetServerSideProps = async ({ req, preview = fa console.log(JSON.stringify(req.headers)) const url = new URL(req.headers.referer || `https://${req.headers.host}${req.url}`).toString() - const serverState = await getServerState( - , - { renderToString }, - ) + const { data } = await getData({ + query: allNewsDocuments, + queryParams, + }) return { props: { - serverState, url, data: { menuData, footerData, intl, - pageData, + pageData: { + ...pageData, + newsArticles: data, + }, slug, }, }, diff --git a/web/pages/nyheter/index.global.tsx b/web/pages/nyheter/index.global.tsx index 113f7bdb7..257504cd9 100644 --- a/web/pages/nyheter/index.global.tsx +++ b/web/pages/nyheter/index.global.tsx @@ -1,39 +1,29 @@ import { GetServerSideProps } from 'next' -import { InstantSearchSSRProvider, getServerState } from 'react-instantsearch' import type { AppProps } from 'next/app' import { IntlProvider } from 'react-intl' import Footer from '../../pageComponents/shared/Footer' import Header from '../../pageComponents/shared/Header' -import { newsroomQuery } from '../../lib/queries/newsroom' +import { allNewsDocuments, newsroomQuery } from '../../lib/queries/newsroom' import getIntl from '../../common/helpers/getIntl' import { getNameFromLocale, getIsoFromLocale } from '../../lib/localization' import { defaultLanguage } from '../../languages' import { AlgoliaIndexPageType, NewsRoomPageType } from '../../types' -import { getComponentsData } from '../../lib/fetchData' -import { renderToString } from 'react-dom/server' -import NewsRoomTemplate from '@templates/newsroom/Newsroom' +import { getComponentsData, getData } from '../../lib/fetchData' +import NewsRoomTemplateSanity from '@templates/newsroom/sanity/NewsroomSanity' -export default function NorwegianNewsRoom({ isServerRendered = false, serverState, data, url }: AlgoliaIndexPageType) { +export default function NorwegianNewsRoom({ data, url }: AlgoliaIndexPageType) { const defaultLocale = defaultLanguage.locale const { pageData, slug, intl } = data const locale = intl?.locale || defaultLocale return ( - - - - - + + + ) } @@ -93,30 +83,23 @@ export const getServerSideProps: GetServerSideProps = async ({ req, preview = fa }, preview, ) - - const serverState = await getServerState( - , - { renderToString }, - ) + const { data } = await getData({ + query: allNewsDocuments, + queryParams, + }) return { props: { locale, - serverState, url, data: { menuData, footerData, intl, - pageData, + pageData: { + ...pageData, + newsArticles: data, + }, slug, }, }, diff --git a/web/templates/newsroom/sanity/NewsHeadlinerSanity.tsx b/web/templates/newsroom/sanity/NewsHeadlinerSanity.tsx new file mode 100644 index 000000000..80a0b2e4f --- /dev/null +++ b/web/templates/newsroom/sanity/NewsHeadlinerSanity.tsx @@ -0,0 +1,77 @@ +import { FormattedDate } from '@components/FormattedDateTime' +import { forwardRef, HTMLAttributes } from 'react' +import { BaseLink } from '@core/Link' +import { Typography } from '@core/Typography' +import Image, { Ratios } from '../../../pageComponents/shared/SanityImage' +import envisTwMerge from '../../../twMerge' +import { NewsRoomNewsItem } from '../../../types/algoliaIndexPage' +import { SanityImageObject } from '@sanity/image-url/lib/types/types' +import Blocks from '../../../pageComponents/shared/portableText/Blocks' + +export type NewsHeadlinerProps = { + data: NewsRoomNewsItem + fallbackImage?: SanityImageObject +} & HTMLAttributes + +const NewsHeadlinerSanity = forwardRef(function NewsHeadlinerSanity( + { data, fallbackImage, className = '', ...rest }, + ref, +) { + const { slug, title, ingress, publishDateTime, heroImage, tags, countryTags } = data + + return ( +
+ + {(heroImage?.image?.asset || fallbackImage) && ( +
+ +
+ )} + {publishDateTime && ( + + )} + {title && ( + + {title} + + )} +
+ {tags?.map((tag: any, i: number) => { + return ( + + {tag.label} + {i < tags.length - 1 && ,} + + ) + })} + {countryTags?.length > 0 && ,} + {countryTags?.map((country: any, i: number) => { + return ( + + {country.label} + {i < countryTags.length - 1 && ,} + + ) + })} +
+ {Array.isArray(ingress) ? ( + + ) : ( + + {ingress} + + )} +
+
+ ) +}) +export default NewsHeadlinerSanity diff --git a/web/templates/newsroom/sanity/NewsItemSanity.tsx b/web/templates/newsroom/sanity/NewsItemSanity.tsx new file mode 100644 index 000000000..0b4e90f2f --- /dev/null +++ b/web/templates/newsroom/sanity/NewsItemSanity.tsx @@ -0,0 +1,86 @@ +import { FormattedDate } from '@components/FormattedDateTime' +import { forwardRef, HTMLAttributes } from 'react' +import { BaseLink } from '@core/Link' +import { Typography } from '@core/Typography' +import Image, { Ratios } from '../../../pageComponents/shared/SanityImage' +import envisTwMerge from '../../../twMerge' +import { NewsRoomNewsItem } from '../../../types/algoliaIndexPage' +import { SanityImageObject } from '@sanity/image-url/lib/types/types' + +export type NewsListItemProps = { + data: NewsRoomNewsItem + fallbackImage?: SanityImageObject +} & HTMLAttributes + +/* Not a semantic list even tho name implies it, used as other news pages with sections */ +const NewsItemSanity = forwardRef(function NewsItemSanity( + { data, fallbackImage, className = '', ...rest }, + ref, +) { + const { slug, title, publishDateTime, heroImage, thumbnailUrl, tags, countryTags } = data || {} + + return ( +
+ +
+ {publishDateTime && ( + + )} + {title && ( + + {title} + + )} +
+ {tags?.map((tag: any, i: number) => { + return ( + + {tag?.label} + {i < tags.length - 1 && ,} + + ) + })} + {countryTags?.length > 0 && ,} + {countryTags?.map((country: any, i: number) => { + return ( + + {country?.label} + {i < countryTags.length - 1 && ,} + + ) + })} +
+
+
+ {(heroImage?.image?.asset || fallbackImage || thumbnailUrl) && ( + <> + {thumbnailUrl ? ( + + ) : ( + (heroImage?.image?.asset || fallbackImage) && ( + + ) + )} + + )} +
+
+
+ ) +}) +export default NewsItemSanity diff --git a/web/templates/newsroom/sanity/NewsSectionsSanity.tsx b/web/templates/newsroom/sanity/NewsSectionsSanity.tsx new file mode 100644 index 000000000..c5dacd811 --- /dev/null +++ b/web/templates/newsroom/sanity/NewsSectionsSanity.tsx @@ -0,0 +1,52 @@ +import { forwardRef } from 'react' +import { FormattedMessage } from 'react-intl' +import envisTwMerge from '../../../twMerge' +import { SanityImageObject } from '@sanity/image-url/lib/types/types' +import { NewsRoomNewsItem } from '../../../types/algoliaIndexPage' +import NewsHeadlinerSanity from './NewsHeadlinerSanity' +import NewsItemSanity from './NewsItemSanity' + +type NewsSectionsProps = { + newslist: NewsRoomNewsItem[] | undefined + fallbackImages?: SanityImageObject[] +} & React.ComponentProps<'div'> + +const NewsSectionsSanity = forwardRef(function NewsSectionsSanity( + { newslist, fallbackImages, className = '' }, + ref, +) { + if (!newslist || newslist?.length === 0) { + return + } + + return ( +
+ +
+ {newslist.map((item: NewsRoomNewsItem, index: number) => { + return index !== 0 ? ( + + ) : null + })} +
+
+ ) +}) + +export default NewsSectionsSanity diff --git a/web/templates/newsroom/sanity/NewsroomSanity.tsx b/web/templates/newsroom/sanity/NewsroomSanity.tsx new file mode 100644 index 000000000..de4345139 --- /dev/null +++ b/web/templates/newsroom/sanity/NewsroomSanity.tsx @@ -0,0 +1,178 @@ +import { forwardRef, useRef, useState } from 'react' +import Blocks from '../../../pageComponents/shared/portableText/Blocks' +import type { NewsRoomPageType } from '../../../types' +import { Heading, Typography } from '@core/Typography' +import { ResourceLink } from '@core/Link' +import Seo from '../../../pageComponents/shared/Seo' +import { FormattedMessage, useIntl } from 'react-intl' +import { List } from '@core/List' +import { PaginationContextProvider } from '../../../common/contexts/PaginationContext' +import NewsSectionsSanity from './NewsSectionsSanity' +import { stringify } from 'querystring' +import { useRouter } from 'next/router' +import { SimplePagination } from '@core/SimplePagination/SimplePagination' +import NewsSectionsSkeleton from '../NewsSections/NewsSectionsSkeleton' +import { getNameFromLocale } from '../../../lib/localization' + +type NewsRoomTemplateProps = { + isServerRendered?: boolean + locale?: string + pageData?: NewsRoomPageType | undefined + slug?: string + url?: string +} + +const NewsRoomTemplateSanity = forwardRef(function NewsRoomTemplateSanity( + { pageData, slug }, + ref, +) { + const { + newsArticles = [], + ingress, + title, + seoAndSome, + subscriptionLink, + subscriptionLinkTitle, + localNewsPages, + fallbackImages, + } = pageData || {} + const intl = useIntl() + const router = useRouter() + const { locale } = router + const resultsRef = useRef(null) + const [isLoading, setIsLoading] = useState(false) + const [lastId, setLastId] = useState(newsArticles?.length > 0 ? newsArticles[newsArticles?.length - 1]?.id : null) + const [firstId, setFirstId] = useState(newsArticles?.length > 0 ? newsArticles[0]?.id : null) + const [firstPublished, setFirstPublished] = useState( + newsArticles?.[0]?.firstPublishedAt ?? newsArticles?.[0]?.publishDateTime ?? null, + ) + const [lastPublished, setLastPublished] = useState( + newsArticles?.[newsArticles?.length - 1]?.firstPublishedAt ?? + newsArticles?.[newsArticles?.length - 1]?.publishDateTime ?? + null, + ) + const [newsList, setNewsList] = useState(newsArticles ?? []) + + const setSearchStates = (filteredNews: any) => { + setNewsList(filteredNews) + setFirstId(filteredNews?.length > 0 ? filteredNews[0].id : null) + setLastId(filteredNews?.length > 0 ? filteredNews[filteredNews.length - 1].id : null) + setFirstPublished( + filteredNews?.length > 0 ? filteredNews[0]?.firstPublishedAt ?? filteredNews[0]?.publishDateTime : null, + ) + setLastPublished( + filteredNews?.length > 0 + ? filteredNews[filteredNews?.length - 1]?.firstPublishedAt ?? + filteredNews[filteredNews?.length - 1]?.publishDateTime + : null, + ) + } + + const getNextNews = async () => { + setIsLoading(true) + const query = { + lang: getNameFromLocale(locale), + lastId: lastId, + lastPublishedAt: lastPublished, + } + const urlParams = stringify(query) + const res = await fetch(`/api/news/next?${urlParams}`) + let filteredNews = [] + try { + const response = await res.json() + filteredNews = response.news + } catch (e) { + console.log('Error', e) + } + setSearchStates(filteredNews) + setIsLoading(false) + } + const getPreviousNews = async () => { + setIsLoading(true) + const query = { + lang: getNameFromLocale(locale), + lastId: firstId, + lastPublishedAt: firstPublished, + } + const urlParams = stringify(query) + const res = await fetch(`/api/news/prev?${urlParams}`) + let filteredNews = [] + try { + const response = await res.json() + filteredNews = response.news + } catch (e) { + console.log('Error', e) + } + setSearchStates(filteredNews) + setIsLoading(false) + } + + return ( + + +
+
+
+
+ {title && } + {ingress && } +
+ + + {subscriptionLink?.slug && ( + + {subscriptionLinkTitle} + + )} + + {localNewsPages && + localNewsPages?.length > 0 && + localNewsPages?.map((localNewsPage) => { + return localNewsPage?.link?.slug ? ( + + + {localNewsPage?.label} + + + ) : null + })} + +
+
+
+
+
+ + + + {isLoading ? ( + + ) : ( + <> + + + + )} +
+
+
+
+
+ ) +}) + +export default NewsRoomTemplateSanity diff --git a/web/types/algoliaIndexPage.ts b/web/types/algoliaIndexPage.ts index 8e76b0688..50c4e84ff 100644 --- a/web/types/algoliaIndexPage.ts +++ b/web/types/algoliaIndexPage.ts @@ -36,6 +36,8 @@ export type NewsRoomNewsItem = { heroImage: ImageWithCaptionData thumbnailUrl?: string ingress?: string + tags?: any + countryTags?: any } export type NewsRoomPageType = { @@ -44,6 +46,7 @@ export type NewsRoomPageType = { ingress?: PortableTextBlock[] subscriptionLink?: { slug: string; type: string; lang: string } subscriptionLinkTitle?: string + newsArticles: NewsRoomNewsItem[] localNewsPages?: LinkData[] fallbackImages?: SanityImageObject[] }