OperationCode/front-end

View on GitHub
pages/podcast.tsx

Summary

Maintainability
A
0 mins
Test Coverage
A
91%
import axios from 'axios';
import get from 'lodash/get';
import dynamic from 'next/dynamic';
import { parse as parseXml } from 'fast-xml-parser';
import { ONE_DAY } from 'common/constants/unitsOfTime';
import Head from 'components/head';
import HeroBanner from 'components/HeroBanner/HeroBanner';
import Card from 'components/Cards/Card/Card';
import Content from 'components/Content/Content';
import Heading from 'components/Heading/Heading';
import styles from 'styles/podcast.module.css';
import Image from 'next/image';

interface RSS {
  channel: {
    item: { image: { href: string }; link: string; title: string; description: string }[];
  };
}

interface Episode {
  image: string;
  name: string;
  source: string;
  story: string;
}

const pageTitle = 'Podcast';

const ReactPlayer = dynamic(() => import('react-player/lazy'), { ssr: false });

// We have atypical error handling because there exist errors thrown on nearly every request.
export async function getStaticProps() {
  const { data } = await axios.get<string>('https://operationcode.libsyn.com/rss');

  const {
    rss,
  }: {
    rss: RSS;
  } = parseXml(data, {
    ignoreNameSpace: true,
    ignoreAttributes: false,
    attributeNamePrefix: '',
  });

  const numberOfEpisodes = get(rss, 'channel.item.length', 0);

  if (numberOfEpisodes > 0) {
    const episodes = rss.channel.item.map(({ image: { href }, link, title, description }) => ({
      image: href,
      name: title,
      source: link,
      story: description.replace(/(<p>|<\/p>)/g, ''),
    }));

    return { props: { episodes }, revalidate: ONE_DAY };
  }

  // Request failed or RSS Feed is broken... Break the build!
  throw new Error('getStaticProps in /podcast failed.');
}

function Podcast({ episodes }: { episodes: Episode[] }) {
  return (
    <div className={styles.Podcast}>
      <Head title={pageTitle} />

      <HeroBanner className={styles.hero} title={pageTitle}>
        <p>Come listen to some inspiring stories of our vets transitioning into tech!</p>
      </HeroBanner>

      <Content
        columns={[
          <div className={styles.podcastCards}>
            {episodes.map(({ name, image, source, story }) => {
              /*
               * Some episodes have multiple parts and are named like "${Name}, part 1".
               * Some episodes are named "${Name} Interview"
               *
               * Parsing them in this manner ensures that the name of the interviewee is
               * available and used for the image alt tag.
               */
              const interviewee = name.replace(/ interview/gi, '').split(',')[0];

              return (
                <Card data-testid="Podcast Card" className={styles.podcastCard} key={name}>
                  <Heading text={interviewee} headingLevel={3} />

                  <Image
                    src={image}
                    alt={interviewee}
                    className={styles.img}
                    width={200}
                    height={200}
                  />

                  <ReactPlayer
                    url={source}
                    controls
                    width="80%"
                    height="65px"
                    config={{
                      file: {
                        attributes: {
                          preload: 'none',
                        },
                      },
                    }}
                  />

                  <p>{story}</p>
                </Card>
              );
            })}
          </div>,
        ]}
      />
    </div>
  );
}

export default Podcast;