src/app/components/RelatedContentSection/index.tsx

Summary

Maintainability
A
55 mins
Test Coverage
A
100%
/** @jsx jsx */

import { useContext } from 'react';
import { jsx, useTheme } from '@emotion/react';
import SectionLabel from '#psammead/psammead-section-label/src';
import pathOr from 'ramda/src/pathOr';
import pathEq from 'ramda/src/pathEq';
import path from 'ramda/src/path';
import tail from 'ramda/src/tail';
import slice from 'ramda/src/slice';
import identity from 'ramda/src/identity';
import last from 'ramda/src/last';
import filter from 'ramda/src/filter';
import pipe from 'ramda/src/pipe';

import useViewTracker from '#hooks/useViewTracker';
import { ServiceContext } from '../../contexts/ServiceContext';
import styles from './index.styles';
import generatePromoId from '../../lib/utilities/generatePromoId';
import RelatedContentItem from './RelatedContentItem';
import PromoList from '../../legacy/components/OptimoPromos/PromoList';
import PromoItem from '../../legacy/components/OptimoPromos/PromoItem/index.styles';
import { EventTrackingBlock } from '../../models/types/eventTracking';
import { OptimoBlock } from '../../models/types/optimo';

const BLOCKS_TO_IGNORE = ['wsoj', 'mpu'];

const removeCustomBlocks = pipe(
  filter((block: OptimoBlock) => !BLOCKS_TO_IGNORE.includes(block.type)),
  last,
);

const isHeadlineFirst = (item: object) => {
  return !!pathOr<string>(
    '',
    ['model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
    item,
  );
};

type RelatedContentListProps = {
  item: object;
  index: number;
  eventTrackingData: EventTrackingBlock;
  viewRef: React.Ref<HTMLDivElement>;
};

const renderRelatedContentList = ({
  item,
  index,
  eventTrackingData,
  viewRef,
}: RelatedContentListProps) => {
  const assetUri = pathOr(
    '',
    [
      'model',
      'blocks',
      1,
      'model',
      'blocks',
      0,
      'model',
      'blocks',
      0,
      'model',
      'locator',
    ],
    item,
  );

  const ariaLabelledBy = generatePromoId({
    sectionType: 'promo-rel-content',
    assetUri,
    index,
  });

  const headlineFirst = isHeadlineFirst(item);

  return (
    <PromoItem
      css={headlineFirst ? styles.promoItemFullWidth : styles.promoItem}
      key={ariaLabelledBy}
    >
      <RelatedContentItem
        item={item}
        ariaLabelledBy={ariaLabelledBy}
        ref={viewRef}
        eventTrackingData={eventTrackingData}
      />
    </PromoItem>
  );
};

const RelatedContentSection = ({ content }: { content: OptimoBlock[] }) => {
  const { translations, script, service } = useContext(ServiceContext);

  const {
    palette: { GREY_2 },
  } = useTheme();

  const blocks = removeCustomBlocks(content);
  const eventTrackingData = {
    block: {
      componentName: 'related-content',
    },
  };
  const eventTrackingDataSend = path<object>(['block'], eventTrackingData);
  const viewRef = useViewTracker(eventTrackingDataSend);

  if (!pathEq('relatedContent', ['type'], blocks)) return null;

  if (!blocks) return null;

  const items = pathOr([], ['model', 'blocks'], blocks);
  const LABEL_ID = 'related-content-heading';

  const customTitle =
    pathEq('title', [0, 'type'], items) &&
    pathOr(
      [],
      [0, 'model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
      items,
    );

  const title =
    customTitle || pathOr('Related Content', ['relatedContent'], translations);

  if (customTitle) tail(items);

  const storyPromoItems = customTitle ? tail(items) : identity(items);

  const reducedStoryPromoItems = slice(0, 6, storyPromoItems);

  const hasSingleContent = reducedStoryPromoItems.length === 1;
  const headlineFirst = isHeadlineFirst(reducedStoryPromoItems[0]);

  const assetUri = pathOr(
    '',
    [
      'model',
      'blocks',
      1,
      'model',
      'blocks',
      0,
      'model',
      'blocks',
      0,
      'model',
      'locator',
    ],
    reducedStoryPromoItems[0],
  );

  const ariaLabelledBy = generatePromoId({
    sectionType: 'promo-rel-content',
    assetUri,
  });

  return (
    <section
      css={styles.relatedContentSection}
      aria-labelledby={LABEL_ID}
      role="region"
      data-e2e={LABEL_ID}
    >
      <SectionLabel
        labelId={LABEL_ID}
        backgroundColor={GREY_2}
        script={script}
        service={service}
      >
        {title}
      </SectionLabel>
      {hasSingleContent ? (
        <div
          css={
            headlineFirst
              ? styles.singleItemWrapperFullWidth
              : styles.singleItemWrapper
          }
        >
          <RelatedContentItem
            item={reducedStoryPromoItems[0]}
            ariaLabelledBy={ariaLabelledBy}
            ref={viewRef}
            eventTrackingData={eventTrackingData}
          />
        </div>
      ) : (
        <PromoList css={styles.relatedContentGrid}>
          {reducedStoryPromoItems.map((item, index) =>
            renderRelatedContentList({
              item,
              index,
              eventTrackingData,
              viewRef,
            }),
          )}
        </PromoList>
      )}
    </section>
  );
};

export default RelatedContentSection;