diff --git a/packages/shared/src/components/highlights/HighlightsPage.tsx b/packages/shared/src/components/highlights/HighlightsPage.tsx index 784d9d1cd61..eb11f9e7566 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.tsx @@ -9,14 +9,17 @@ import type { import { channelHighlightsFeedQueryOptions, highlightsPageQueryOptions, + postHighlightsFeedQueryOptions, } from '../../graphql/highlights'; import { Tab, TabContainer } from '../tabs/TabContainer'; import { DigestCTA } from './DigestCTA'; import { HighlightItem } from './HighlightItem'; const MAJOR_HEADLINES_LABEL = 'Headlines'; +const ALL_HIGHLIGHTS_LABEL = 'All'; const SKELETON_COUNT = 5; const HIGHLIGHTS_BASE_URL = '/highlights'; +const ALL_HIGHLIGHTS_URL = `${HIGHLIGHTS_BASE_URL}/all`; const HighlightSkeleton = (): ReactElement => (
@@ -124,10 +127,36 @@ const ChannelTab = ({ ); }; +const AllHighlightsTab = ({ + active, + expandedId, +}: { + active: boolean; + expandedId?: string; +}): ReactElement => { + const { data, isFetching } = useQuery({ + ...postHighlightsFeedQueryOptions(), + enabled: active, + }); + const highlights = useMemo( + () => data?.postHighlightsFeed?.edges?.map((edge) => edge.node) ?? [], + [data], + ); + + return ( + + ); +}; + export const HighlightsPage = (): ReactElement => { const router = useRouter(); const channel = getSingleQueryParam(router.query.channel); const expandedId = getSingleQueryParam(router.query.highlight); + const isAllTab = router.pathname === ALL_HIGHLIGHTS_URL; const { data, isFetching } = useQuery(highlightsPageQueryOptions()); const channels = data?.channelConfigurations ?? []; @@ -137,9 +166,10 @@ export const HighlightsPage = (): ReactElement => { ); const majorLoading = isFetching && !data; - const activeTab = - channels.find((c) => c.channel === channel)?.displayName ?? - MAJOR_HEADLINES_LABEL; + const channelLabel = channels.find((c) => c.channel === channel)?.displayName; + const activeTab = isAllTab + ? ALL_HIGHLIGHTS_LABEL + : channelLabel ?? MAJOR_HEADLINES_LABEL; return (
@@ -172,6 +202,9 @@ export const HighlightsPage = (): ReactElement => { expandedId={expandedId} /> , + + + , ...channels.map((ch) => ( ; +} + +const POST_HIGHLIGHTS_FEED_PAGE_QUERY = gql` + query PostHighlightsFeedPage( + $channel: String + $significance: [String!] + $first: Int + $after: String + ) { + postHighlightsFeed( + channel: $channel + significance: $significance + first: $first + after: $after + ) { + pageInfo { + endCursor + hasNextPage + } + edges { + node { + ...PostHighlightFeedCard + } + } + } + } + ${POST_HIGHLIGHT_FEED_FRAGMENT} +`; + +export const postHighlightsFeedQueryOptions = ({ + channel, + significance, + first = MAJOR_HEADLINES_MAX_FIRST, + after, +}: { + channel?: string; + significance?: PostHighlightSignificance[]; + first?: number; + after?: string; +} = {}) => ({ + queryKey: [ + 'post-highlights-feed', + channel ?? '', + [...(significance ?? [])].sort().join(','), + first, + after ?? '', + ], + queryFn: () => + gqlClient.request( + POST_HIGHLIGHTS_FEED_PAGE_QUERY, + { + channel: channel ?? null, + significance: significance ?? null, + first, + after, + }, + ), + staleTime: ONE_MINUTE, +}); + export interface ChannelDigestConfiguration { frequency: string; source?: { diff --git a/packages/webapp/pages/highlights/all.tsx b/packages/webapp/pages/highlights/all.tsx new file mode 100644 index 00000000000..b6e4c2685b6 --- /dev/null +++ b/packages/webapp/pages/highlights/all.tsx @@ -0,0 +1,64 @@ +import type { GetStaticPropsResult } from 'next'; +import type { ReactElement } from 'react'; +import React from 'react'; +import type { DehydratedState } from '@tanstack/react-query'; +import { dehydrate, QueryClient } from '@tanstack/react-query'; +import { + highlightsPageQueryOptions, + postHighlightsFeedQueryOptions, +} from '@dailydotdev/shared/src/graphql/highlights'; +import { HighlightsPage } from '@dailydotdev/shared/src/components/highlights/HighlightsPage'; +import { getLayout as getFooterNavBarLayout } from '../../components/layouts/FooterNavBarLayout'; +import { getLayout } from '../../components/layouts/MainLayout'; +import { defaultOpenGraph, defaultSeo } from '../../next-seo'; + +const HIGHLIGHTS_TITLE = 'All highlights | daily.dev'; +const HIGHLIGHTS_DESCRIPTION = + 'Every highlight across the developer ecosystem in one place. Browse the most recent stories from every channel, newest first.'; + +const AllHighlightsPage = (): ReactElement => ; + +const getHighlightsLayout: typeof getLayout = (...props) => + getFooterNavBarLayout(getLayout(...props)); + +AllHighlightsPage.getLayout = getHighlightsLayout; +AllHighlightsPage.layoutProps = { + screenCentered: false, + seo: { + title: HIGHLIGHTS_TITLE, + description: HIGHLIGHTS_DESCRIPTION, + canonical: 'https://app.daily.dev/highlights/all', + openGraph: { + ...defaultOpenGraph, + title: HIGHLIGHTS_TITLE, + description: HIGHLIGHTS_DESCRIPTION, + url: 'https://app.daily.dev/highlights/all', + type: 'website', + }, + ...defaultSeo, + }, +}; + +export default AllHighlightsPage; + +interface AllHighlightsPageProps { + dehydratedState: DehydratedState; +} + +export async function getStaticProps(): Promise< + GetStaticPropsResult +> { + const queryClient = new QueryClient(); + + await Promise.all([ + queryClient.prefetchQuery(highlightsPageQueryOptions()), + queryClient.prefetchQuery(postHighlightsFeedQueryOptions()), + ]); + + return { + props: { + dehydratedState: dehydrate(queryClient), + }, + revalidate: 60, + }; +}