diff --git a/src/app/components/Curation/HighImpactPromo/index.stories.tsx b/src/app/components/Curation/HighImpactPromo/index.stories.tsx index fe448f244ae..df2bbe73334 100644 --- a/src/app/components/Curation/HighImpactPromo/index.stories.tsx +++ b/src/app/components/Curation/HighImpactPromo/index.stories.tsx @@ -6,6 +6,27 @@ import readme from './README.md'; const highImpactFixtureCuration = fixture.data.curations[0] as BaseCuration; +const topicTags = [ + { + topicId: 'c404v061z85t', + topicName: 'Africa', + subjectList: [], + curationList: [], + types: [], + home: '', + topicUrl: '', + }, + { + topicId: 'c2dwqd1zr92t', + topicName: 'Nigeria', + subjectList: [], + curationList: [], + types: [], + home: '', + topicUrl: '', + }, +]; + const Component = () => { return (
{ maxWidth: '480px', }} > + {/* Default attribution (service-based) */} { text: 'BBC News Pidgin', }} /> + {/* Attribution with primaryTopic (topic-based) */} + + {/* Attribution with custom attribution prop */} { expect(attributionLink).toHaveAttribute('href', '/pidgin'); }); + it('should render topic-based attribution if topics are present', () => { + const topics = [ + { + topicId: 'c404v061z85t', + topicName: 'Africa', + subjectList: [], + curationList: [], + types: [], + home: '', + topicUrl: '', + }, + { + topicId: 'c2dwqd1zr92t', + topicName: 'Nigeria', + subjectList: [], + curationList: [], + types: [], + home: '', + topicUrl: '', + }, + ]; + const promoWithTopics = { ...promoFixtureData, topics }; + render(, { + service: 'pidgin', + pageType: 'home', + // @ts-expect-error: translations is not in the official type but is supported by the test utils + translations: { topicsPath: 'topics' }, + }); + + const attributionLink = screen.getByRole('link', { name: 'Africa' }); + expect(attributionLink).toBeInTheDocument(); + expect(attributionLink).toHaveAttribute( + 'href', + getTopicPageUrl('c404v061z85t', 'pidgin'), + ); + }); + + it('should fall back to service attribution if topics is empty', () => { + const promoWithNoTopics = { ...promoFixtureData, topics: [] }; + render(, { service: 'pidgin' }); + const attributionLink = screen.getByRole('link', { + name: 'BBC News Pidgin', + }); + expect(attributionLink).toBeInTheDocument(); + expect(attributionLink).toHaveAttribute('href', '/pidgin'); + }); + it.each<[Services, string]>([ ['mundo', 'ltr'], ['arabic', 'rtl'], diff --git a/src/app/components/Curation/HighImpactPromo/index.tsx b/src/app/components/Curation/HighImpactPromo/index.tsx index 5f4f8a64a7e..13622133867 100644 --- a/src/app/components/Curation/HighImpactPromo/index.tsx +++ b/src/app/components/Curation/HighImpactPromo/index.tsx @@ -1,10 +1,12 @@ import { use } from 'react'; +import pathOr from 'ramda/src/pathOr'; import { Summary } from '#app/models/types/curationData'; import Promo from '#components/Promo'; import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler'; import { RequestContext } from '#app/contexts/RequestContext'; import { ServiceContext } from '#app/contexts/ServiceContext'; import { getBrandPath } from '#app/legacy/containers/Brand'; +import getTopicPageUrl from '#app/utilities/getTopicPageUrl'; import styles from './index.styles'; type Attribution = { @@ -24,13 +26,19 @@ const HighImpactPromo = ({ headingLevel = 3, eventTrackingData, attribution, + primaryTopic, }: HighImpactPromoProps) => { - const { isAmp } = use(RequestContext); - const { dir, service, brandName } = use(ServiceContext) || {}; + const { isAmp, variant } = use(RequestContext); + const { dir, service, brandName, translations } = use(ServiceContext) || {}; - const attributionLink = - attribution?.link || (service ? getBrandPath(service) : null); - const attributionText = attribution?.text || brandName; + const topicsPath = pathOr('topics', ['topicsPath'], translations); + + // Use primaryTopic for attribution if available, otherwise fall back to default attribution + const attributionLink = primaryTopic?.topicId + ? getTopicPageUrl(primaryTopic.topicId, service, variant, topicsPath) + : attribution?.link || (service ? getBrandPath(service) : null); + const attributionText = + primaryTopic?.topicName || attribution?.text || brandName; const hasAttribution = attributionLink && attributionText; const clickTrackerHandler = useClickTrackerHandler(eventTrackingData); diff --git a/src/app/components/TopicTags/index.tsx b/src/app/components/TopicTags/index.tsx index 2c077bb80d3..862a58ebd73 100644 --- a/src/app/components/TopicTags/index.tsx +++ b/src/app/components/TopicTags/index.tsx @@ -1,4 +1,5 @@ import { use } from 'react'; +import getTopicPageUrl from '#app/utilities/getTopicPageUrl'; import { ServiceContext } from '#app/contexts/ServiceContext'; import { RequestContext } from '#app/contexts/RequestContext'; import { TopicTag } from '#app/models/types/metadata'; @@ -23,14 +24,6 @@ export const TopicTags = ({ const { service, translations } = use(ServiceContext); const { variant } = use(RequestContext); - const getTopicPageUrl = (id: string) => { - const isPublicService = ['news', 'cymrufyw', 'naidheachdan']; - const hostname = `https://www.bbc.${isPublicService.includes(service) ? 'co.uk' : 'com'}`; - const topicsPath = translations?.topicsPath ?? 'topics'; - - return `${hostname}/${service}/${topicsPath}/${id}${variant ? `/${variant}` : ''}`; - }; - const hasMultiple = tags.length > 1; return hasMultiple ? ( @@ -42,7 +35,15 @@ export const TopicTags = ({ > {tags.map(tag => (
  • - + {tag.topicName}
  • @@ -55,7 +56,15 @@ export const TopicTags = ({ {...viewTracker} > diff --git a/src/app/models/types/curationData.ts b/src/app/models/types/curationData.ts index 7a545f84d22..28027bf38a6 100644 --- a/src/app/models/types/curationData.ts +++ b/src/app/models/types/curationData.ts @@ -6,6 +6,7 @@ import { import { RadioScheduleData } from '#app/models/types/radioSchedule'; import { EventTrackingData } from '#app/lib/analyticsUtils/types'; import { MostReadData } from '../../components/MostRead/types'; +import { TopicTag } from './metadata'; // This maps to the Summary type definition from the BFF interface BaseSummary { @@ -33,6 +34,7 @@ export interface Summary extends BaseSummary { timeOfDayExperimentName?: string | null; timeOfDayVariant?: string | null; isPortraitImage?: boolean; + primaryTopic?: TopicTag; } export const VISUAL_STYLE = { diff --git a/src/app/utilities/getTopicPageUrl/index.ts b/src/app/utilities/getTopicPageUrl/index.ts new file mode 100644 index 00000000000..05014a4b94f --- /dev/null +++ b/src/app/utilities/getTopicPageUrl/index.ts @@ -0,0 +1,22 @@ +import { Variants } from '#app/models/types/global'; + +/** + * Returns the canonical topic page URL for a given topic ID, service, and variant. + * @param topicId - The topic ID (e.g. 'c404v061z85t') + * @param service - The BBC service (e.g. 'pidgin') + * @param variant - Optional variant (e.g. 'simp') + * @param topicsPath - The path segment for topics (e.g. 'topics', 'pynciau') + * @returns The full topic page URL + */ +export const getTopicPageUrl = ( + topicId: string, + service: string, + variant?: Variants | null, + topicsPath = 'topics', +) => { + const isPublicService = ['news', 'cymrufyw', 'naidheachdan']; + const hostname = `https://www.bbc.${isPublicService.includes(service) ? 'co.uk' : 'com'}`; + return `${hostname}/${service}/${topicsPath}/${topicId}${variant ? `/${variant}` : ''}`; +}; + +export default getTopicPageUrl;