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,
+ };
+}