src/pages/Notifications/index.tsx (162 lines of code) (raw):

import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import spacetime from 'spacetime'; import styled from 'styled-components'; import { apiHttp } from '@/constants'; import { AsyncStatus, Announcement as IAnnouncement, AnnouncementHeader as IAnnouncementHeader } from '@/types'; import { MessageRender } from '@components/Announcement'; import ContentHeader from '@components/Content/ContentHeader'; import ContentWrapper from '@components/Content/ContentWrapper'; import ErrorBoundary from '@components/GeneralErrorBoundary'; import GenericError from '@components/GenericError'; import Icon from '@components/Icon'; import Spinner from '@components/Spinner'; import { ItemRow } from '@components/Structure'; import { logWarning } from '@utils/errorlogger'; import LaunchIconBlack from '@assets/launch_black.svg'; const Notifications: React.FC = () => { const { t } = useTranslation(); const [status, setStatus] = useState<AsyncStatus>('Loading'); const [announcements, setAnnouncements] = useState<IAnnouncement[]>([]); const [combinedArray, setCombinedArray] = useState<(IAnnouncement | IAnnouncementHeader)[]>([]); useEffect(() => { setStatus('Loading'); fetch(apiHttp(`/notifications`), { headers: { 'Content-Type': 'application/json', }, }) .then((response) => { if (response.status === 200) { return response.json().then((data) => { if (Array.isArray(data)) { data.sort((a, b) => (b.start || b.created) - (a.start || a.created)); setAnnouncements(data); setStatus('Ok'); } else { logWarning('Failed to fetch announcements.'); } }); } }) .catch((e) => { logWarning('Failed to fetch announcements.', e); setStatus('Error'); }); }, []); useEffect(() => { const parseHeader = (time: number | undefined) => { if (time === undefined) { return t('notifications.unsorted'); } const dateString = spacetime(time).format('{day}, {date-pad}.{month-iso}.{year}'); return dateString as string; }; announcements.forEach((result, index, array) => { // check when the dates day changes if ( spacetime(result.created as number).date() !== spacetime(array[index - 1]?.created as number).date() || index === 0 ) { // push a header into the array setCombinedArray((combinedArray) => [ ...combinedArray, { id: (new Date().getTime() + index).toString(), message: parseHeader((result.start !== null && result.start) || result.created), type: 'header', }, ]); } return setCombinedArray((combinedArray) => [...combinedArray, result]); }); }, [announcements, t]); const parsePublished = (time: number | undefined) => { if (time === undefined) { return t('notifications.dateMissing'); } const dateString = spacetime(time).format('{date-pad}.{month-iso}.{year}, {hour-24-pad}.{minute-pad} GMT{offset}'); return dateString; }; return ( <div style={{ display: 'flex', flex: 1 }}> <ErrorBoundary message={'notifications error'}> <Content> <Header>{t('notifications.header')}</Header> {status === 'Loading' && ( <div style={{ textAlign: 'center', margin: '2rem 0' }}> <Spinner md /> </div> )} {status === 'Ok' && combinedArray.length === 0 && ( <ItemRow margin="md"> <GenericError icon="searchNotFound" message={t('error.no-results')} /> </ItemRow> )} {status === 'Ok' && ( <div data-testid={'notification-results'}> {combinedArray.map((announcement) => { if (announcement.type === 'header') { return <ContentHeader key={announcement.id}>{announcement.message}</ContentHeader>; } else { return ( <ContentWrapper data-testid={'notification-result'} key={announcement.id}> <IconWrapper> <Icon name="warningThick" size="md" /> </IconWrapper> <MessageWrapper> <MessageRender item={announcement as IAnnouncement} /> <Published>{`${t('notifications.published')} ${parsePublished( ((announcement as IAnnouncement).start !== null && (announcement as IAnnouncement).start) || (announcement as IAnnouncement).created, )}`}</Published> </MessageWrapper> </ContentWrapper> ); } })} </div> )} </Content> </ErrorBoundary> </div> ); }; export default Notifications; const Content = styled.div` display: flex; flex-direction: column; flex: 1; max-width: 100%; position: relative; `; const Header = styled.h3` margin: 1rem 0; `; const IconWrapper = styled.div` color: #000; padding: 0.25rem; `; const MessageWrapper = styled.div` flex-direction: column; margin: 0 0 0 1rem; .markdown { font-size: 1rem; font-weight: 500; line-height: 2rem; } .markdown a { color: inherit; &::after { content: url(${LaunchIconBlack}); display: inline-block; margin: 0 0 0 0.25rem; position: relative; top: 0.25rem; } } `; const Published = styled.span` color: #666; display: block; font-size: 0.625rem; line-height: 1rem; `;