- {topContributors.map((contributor) => (
+ )}
+ {recommendedTags.length > 0 && (
+
+ )}
+ {topContributors.length > 0 && (
+
+ )}
+ >
+ );
+
+ const roadmapNode =
+ showRoadmap && initialData?.flags?.roadmap ? (
+
+
+
+
+
+ Comprehensive roadmap for {tag}
+
+
By roadmap.sh
+ }
+ size={ButtonSize.Small}
+ variant={ButtonVariant.Tertiary}
+ />
+
+
+ ) : null;
+
+ const jsonLdHead = jsonLd ? (
+
+
+
+ ) : null;
+
+ const breadcrumbs = (
+
+ );
+
+ const relatedEntities = (
+ <>
+
+
+ >
+ );
+
+ const archiveCard = (
+
+ );
+
+ const allPostsFeed = (
+
+ );
+
+ if (isRedesign) {
+ return (
+
+ {jsonLdHead}
+
+ {breadcrumbs}
+
}
+ onGetFeed={onGetFeed}
+ occurrences={initialData?.occurrences}
+ contributorsCount={topContributors.length}
+ >
+ {seoLinks}
+
+
+
+
+
+
Latest in #{title}
+
+ Newest first
+
+
+ {archiveCard}
+ {allPostsFeed}
+
+ {shouldShowAuthBanner && isLaptop &&
}
+
+
+ );
+ }
+
+ return (
+
+ {jsonLdHead}
+ {breadcrumbs}
+
+
+
+
+
{title}
+
+ {headerActions}
+ {initialData?.flags?.description && (
+ {initialData?.flags?.description}
)}
+ {seoLinks}
{tag && (
)}
- {showRoadmap && initialData?.flags?.roadmap && (
-
-
-
-
-
- Comprehensive roadmap for {tag}
-
-
- By roadmap.sh
-
-
- }
- size={ButtonSize.Small}
- variant={ButtonVariant.Tertiary}
- />
-
-
- )}
+ {roadmapNode}
-
-
+ {relatedEntities}
@@ -624,28 +793,13 @@ const TagPage = ({
emptyScreen={<>>}
/>
-
+ {archiveCard}
-
+ {allPostsFeed}
);
};
diff --git a/packages/webapp/pages/tags/index.tsx b/packages/webapp/pages/tags/index.tsx
index f67b09efb5..827759cdc5 100644
--- a/packages/webapp/pages/tags/index.tsx
+++ b/packages/webapp/pages/tags/index.tsx
@@ -1,5 +1,6 @@
import type { ReactElement } from 'react';
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
+import classNames from 'classnames';
import type { GetStaticPropsResult } from 'next';
import Head from 'next/head';
import type { NextSeoProps } from 'next-seo/lib/types';
@@ -15,6 +16,16 @@ import type { GraphQLError } from '@dailydotdev/shared/src/lib/errors';
import { PageWrapperLayout } from '@dailydotdev/shared/src/components/layout/PageWrapperLayout';
import { TagTopList } from '@dailydotdev/shared/src/components/cards/Leaderboard';
import useFeedSettings from '@dailydotdev/shared/src/hooks/useFeedSettings';
+import { useFeature } from '@dailydotdev/shared/src/components/GrowthBookProvider';
+import { featureTagPageRedesign } from '@dailydotdev/shared/src/lib/featureManagement';
+import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
+import { AuthTriggers } from '@dailydotdev/shared/src/lib/auth';
+import {
+ Button,
+ ButtonSize,
+ ButtonVariant,
+} from '@dailydotdev/shared/src/components/buttons/Button';
+import { SearchField } from '@dailydotdev/shared/src/components/fields/SearchField';
import { getLayout as getFooterNavBarLayout } from '../../components/layouts/FooterNavBarLayout';
import { getLayout } from '../../components/layouts/MainLayout';
import { defaultOpenGraph } from '../../next-seo';
@@ -67,6 +78,9 @@ const TagsPage = ({
popularTags,
}: TagsPageProps): ReactElement => {
const { isFallback: isLoading } = useRouter();
+ const isRedesign = useFeature(featureTagPageRedesign);
+ const { user, showLogin } = useAuthContext();
+ const [search, setSearch] = useState('');
const { feedSettings } = useFeedSettings();
const followedSet = useMemo(
@@ -113,10 +127,38 @@ const TagsPage = ({
}, {});
}, [tags]);
+ // Client-side filter for the A-Z wall. The full list is always in the SSG
+ // HTML (empty query), so crawlers still see every tag link.
+ const displayTagsByFirstLetter = useMemo(() => {
+ if (!tagsByFirstLetter) {
+ return null;
+ }
+ const query = search.trim().toLowerCase();
+ if (!query) {
+ return tagsByFirstLetter;
+ }
+ return Object.entries(tagsByFirstLetter).reduce
>(
+ (acc, [letter, value]) => {
+ const matches = value.filter((tag) =>
+ tag.value.toLowerCase().includes(query),
+ );
+ if (matches.length) {
+ acc[letter] = matches;
+ }
+ return acc;
+ },
+ {},
+ );
+ }, [tagsByFirstLetter, search]);
+
if (isLoading) {
return <>>;
}
+ const hasFilteredResults =
+ !!displayTagsByFirstLetter &&
+ Object.keys(displayTagsByFirstLetter).length > 0;
+
const topTagsForSchema = tags.slice(0, 50);
return (
@@ -132,6 +174,32 @@ const TagsPage = ({
Tags
+ {isRedesign && (
+
+ Explore developer tags
+
+ Browse trending, popular, and new topics on daily.dev โ and turn the
+ ones you care about into a personalized feed, joined by millions of
+ developers.
+
+ {!user && (
+
+ )}
+
+ )}
-
+
All tags
+ {isRedesign && (
+
+ )}
- {tagsByFirstLetter &&
- Object.entries(tagsByFirstLetter).map(([letter, value]) => {
+ {displayTagsByFirstLetter &&
+ Object.entries(displayTagsByFirstLetter).map(([letter, value]) => {
return (
+ {isRedesign && search.trim() && !hasFilteredResults && (
+
+ No tags match โ{search.trim()}โ.
+
+ )}
);
};