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;