Skip to content

Commit

Permalink
646-fix: Stale courses date (#647)
Browse files Browse the repository at this point in the history
* refactor: 646 - change dates to be calculated on the client

* chore: 646 - add env variable to percy workflows

* refactor: 646 - replace magic string with constant

* refactor: 646 - change courses to become stale after registration end
  • Loading branch information
Quiddlee authored Nov 15, 2024
1 parent b28c370 commit 15835e5
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 37 deletions.
5 changes: 4 additions & 1 deletion src/entities/course/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export type Course = {

export type CourseStatus = 'planned' | 'available' | 'upcoming';

export type CourseItemData = Pick<Course, 'title' | 'language' | 'startDate' | 'detailsUrl'> & {
export type CourseItemData = Pick<
Course,
'title' | 'language' | 'startDate' | 'detailsUrl' | 'registrationEndDate'
> & {
iconSrc: StaticImageData;
};
4 changes: 3 additions & 1 deletion src/entities/course/ui/course-card/course-card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { beforeEach, describe, expect, it } from 'vitest';
import { CourseCard, type CourseCardProps, cx } from './course-card';
import { MOCKED_IMAGE_PATH } from '@/shared/__tests__/constants';
import { renderWithRouter } from '@/shared/__tests__/utils';
import { dayJS } from '@/shared/helpers/dayJS';
import { COURSE_TITLES } from 'data';

describe('CourseCard', () => {
const mockProps: CourseCardProps = {
title: COURSE_TITLES.REACT,
iconSrc: MOCKED_IMAGE_PATH,
startDate: '2023-01-01',
startDate: dayJS().toISOString(),
registrationEndDate: dayJS().add(1, 'd').toISOString(),
mode: 'online',
language: ['en'],
detailsUrl: 'http://example.com/course',
Expand Down
17 changes: 15 additions & 2 deletions src/entities/course/ui/course-card/course-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ export const cx = classNames.bind(styles);

export type CourseCardProps = Pick<
Course,
'title' | 'iconSrc' | 'startDate' | 'detailsUrl' | 'mode' | 'language' | 'backgroundStyle'
| 'title'
| 'iconSrc'
| 'startDate'
| 'detailsUrl'
| 'mode'
| 'language'
| 'backgroundStyle'
| 'registrationEndDate'
>;

export const CourseCard = ({
title,
iconSrc,
startDate,
registrationEndDate,
detailsUrl,
mode,
language,
Expand All @@ -37,7 +45,12 @@ export const CourseCard = ({
<Subtitle fontSize="small">{title}</Subtitle>
</div>
<div className={cx('course-info')}>
<DateLang startDate={startDate} language={language} mode={mode} />
<DateLang
startDate={startDate}
registrationEndDate={registrationEndDate}
language={language}
mode={mode}
/>
<LinkCustom
href={detailsUrl}
variant="rounded"
Expand Down
4 changes: 3 additions & 1 deletion src/entities/course/ui/course-item/course-item.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import dayjs from 'dayjs';
import { CourseItem, CourseItemData } from '@/entities/course';
import { MOCKED_IMAGE_PATH } from '@/shared/__tests__/constants';
import { renderWithRouter } from '@/shared/__tests__/utils';
import { dayJS } from '@/shared/helpers/dayJS';
import { COURSE_TITLES } from 'data';

const mockedProps: CourseItemData = {
title: COURSE_TITLES.REACT,
language: ['en'],
startDate: '2024-05-01',
startDate: dayJS().toISOString(),
registrationEndDate: dayJS().add(1, 'd').toISOString(),
detailsUrl: '/courses/react-intro',
iconSrc: MOCKED_IMAGE_PATH,
};
Expand Down
21 changes: 14 additions & 7 deletions src/entities/course/ui/course-item/course-item.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import classNames from 'classnames/bind';
import dayjs from 'dayjs';
import Image from 'next/image';
import { CourseItemData } from '@/entities/course';
import { DateStart } from '@/shared/ui/date-start';
import { LinkCustom } from '@/shared/ui/link-custom';
import { Subtitle } from '@/shared/ui/subtitle';

import styles from './course-item.module.scss';

const cx = classNames.bind(styles);

export const CourseItem = ({ title, language, startDate, detailsUrl, iconSrc }: CourseItemData) => {
const dateTime = dayjs(startDate).toISOString();

export const CourseItem = ({
title,
language,
startDate,
registrationEndDate,
detailsUrl,
iconSrc,
}: CourseItemData) => {
return (
<section className={cx('course-item')}>
<figure className={cx('icon-container')}>
Expand All @@ -28,9 +33,11 @@ export const CourseItem = ({ title, language, startDate, detailsUrl, iconSrc }:
{title}
</Subtitle>
<p className={cx('date')}>
<time dateTime={dateTime} data-testid="course-date">
{startDate}
</time>
<DateStart
courseStartDate={startDate}
registrationEndDate={registrationEndDate}
data-testid="course-date"
/>
<span data-testid="course-language">{` • ${language[0].toUpperCase()}`}</span>
</p>
</article>
Expand Down
4 changes: 2 additions & 2 deletions src/shared/__tests__/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const mockedCourses: Course[] = [
title: COURSE_TITLES.JS_PRESCHOOL_RU,
alias: COURSE_ALIASES.JS_PRESCHOOL_RU,
startDate: 'Jun 24, 2024',
registrationEndDate: 'Jun 24, 2025',
registrationEndDate: 'Jun 24, 2024',
language: ['ru'],
detailsUrl: `/${ROUTES.COURSES}/${ROUTES.JS_PRESCHOOL_RU}`,
iconSrc: MOCKED_IMAGE_PATH,
Expand Down Expand Up @@ -84,7 +84,7 @@ export const mockedCourses: Course[] = [
title: COURSE_TITLES.REACT,
alias: COURSE_ALIASES.REACT,
startDate: 'Jul 1, 2024',
registrationEndDate: 'Jun 24, 2025',
registrationEndDate: 'Jun 24, 2024',
language: ['en'],
detailsUrl: `/${ROUTES.COURSES}/${ROUTES.REACT}`,
iconSrc: MOCKED_IMAGE_PATH,
Expand Down
8 changes: 6 additions & 2 deletions src/shared/helpers/getActualData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ type DataType = Course[] | Event[];

type GetActualDataParams<T extends DataType> = {
data: T;
staleAfter: number;
staleAfter?: number;
filterStale?: boolean;
};

type GetActualDataType = <T extends DataType>(params: GetActualDataParams<T>) => T;

export const getActualData: GetActualDataType = ({ data, staleAfter, filterStale = true }) => {
let dataWithTBD = mapStaleAsTBD(data, staleAfter);
let dataWithTBD = data;

if (staleAfter) {
dataWithTBD = mapStaleAsTBD(data, staleAfter);
}

if (filterStale) {
dataWithTBD = filterStaleData(dataWithTBD);
Expand Down
15 changes: 12 additions & 3 deletions src/shared/ui/date-lang/date-lang.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@ import { describe, expect, it } from 'vitest';
import { DateLang } from './date-lang';
import micIcon from '@/shared/assets/icons/mic.svg';
import noteIcon from '@/shared/assets/icons/note-icon.svg';
import { dayJS } from '@/shared/helpers/dayJS';

describe('DateLang', () => {
it('renders the start date correctly', () => {
const startDate = '2060-01-01';
const registrationEndDate = dayJS(startDate).add(1, 'd').toISOString();

render(<DateLang startDate={startDate} language={[]} mode="" />);
render(
<DateLang
startDate={startDate}
registrationEndDate={registrationEndDate}
language={[]}
mode=""
/>,
);
expect(screen.getByText(`${startDate}`)).toBeInTheDocument();
});

it('renders the mode correctly', () => {
const mode = 'Online';

render(<DateLang startDate="" language={[]} mode={mode} />);
render(<DateLang startDate="" registrationEndDate="" language={[]} mode={mode} />);
expect(screen.getByText(`${mode}`)).toBeInTheDocument();
});

it('displays the correct note and microphone icons', () => {
render(<DateLang startDate="" language={[]} mode="" />);
render(<DateLang startDate="" registrationEndDate="" language={[]} mode="" />);
expect(screen.getByAltText('note-icon')).toHaveAttribute('src', noteIcon.src);
expect(screen.getByRole('img', { name: 'microphone-icon' })).toHaveAttribute(
'src',
Expand Down
15 changes: 10 additions & 5 deletions src/shared/ui/date-lang/date-lang.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import classNames from 'classnames/bind';
import dayjs from 'dayjs';
import Image from 'next/image';
import micIcon from '@/shared/assets/icons/mic.svg';
import noteIcon from '@/shared/assets/icons/note-icon.svg';
import { DateStart } from '@/shared/ui/date-start';

import styles from './date-lang.module.scss';

const cx = classNames.bind(styles);

interface DateLangProps {
startDate: string;
registrationEndDate: string;
mode: string;
language: string[];
withMargin?: boolean;
}

export const DateLang = ({ startDate, language, mode, withMargin }: DateLangProps) => {
const dateAttr = dayjs(startDate).format('YYYY-MM-DD');

export const DateLang = ({
startDate,
registrationEndDate,
language,
mode,
withMargin,
}: DateLangProps) => {
return (
<section className={cx('info', { margin: withMargin })}>
<p className={cx('date')}>
<Image className={cx('icon')} src={noteIcon} alt="note-icon" />
<span>Start:</span>
<time dateTime={dateAttr}>{startDate}</time>
<DateStart courseStartDate={startDate} registrationEndDate={registrationEndDate} />
</p>
<p className={cx('additional-info')}>
<Image className={cx('icon')} src={micIcon} alt="microphone-icon" />
Expand Down
1 change: 1 addition & 0 deletions src/shared/ui/date-start/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DateStart } from './ui/date-start';
28 changes: 28 additions & 0 deletions src/shared/ui/date-start/ui/date-start.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import React, { HTMLProps } from 'react';
import { TO_BE_DETERMINED } from '@/shared/constants';
import { dayJS } from '@/shared/helpers/dayJS';
import { getCourseDate } from '@/shared/helpers/getCourseDate';

type DateStartProps = HTMLProps<HTMLTimeElement> & {
courseStartDate: string;
registrationEndDate: string;
};

export const DateStart = ({ courseStartDate, registrationEndDate, ...props }: DateStartProps) => {
const staleDays = dayJS(registrationEndDate).diff(courseStartDate, 'days');

const freshDate = getCourseDate(courseStartDate, staleDays);
let dateAttr = undefined;

if (freshDate !== TO_BE_DETERMINED) {
dateAttr = dayJS(freshDate).toISOString();
}

return (
<time {...props} dateTime={dateAttr}>
{freshDate}
</time>
);
};
2 changes: 0 additions & 2 deletions src/widgets/courses/ui/courses.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import classNames from 'classnames/bind';
import { COURSE_STALE_AFTER_DAYS } from '@/core/const';
import { type Course, CourseCard } from '@/entities/course';
import { getCourses } from '@/entities/course/api/course-api';
import { getActualData } from '@/shared/helpers/getActualData';
Expand All @@ -14,7 +13,6 @@ export const Courses = async () => {

const sortParams = {
data: courses,
staleAfter: COURSE_STALE_AFTER_DAYS,
filterStale: false,
};

Expand Down
11 changes: 9 additions & 2 deletions src/widgets/hero-course/ui/hero-course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ type HeroCourseProps = {
};

export const HeroCourse = ({ lang = 'en', type, course }: HeroCourseProps) => {
const { title, altTitle, language, mode, enroll, secondaryIcon, startDate } = course;
const { title, altTitle, language, mode, enroll, secondaryIcon, startDate, registrationEndDate } =
course;
const status = getCourseStatus(startDate);
const date = getCourseDate(startDate, COURSE_STALE_AFTER_DAYS);

Expand All @@ -33,7 +34,13 @@ export const HeroCourse = ({ lang = 'en', type, course }: HeroCourseProps) => {
<SectionLabel data-testid="course-label">{status}</SectionLabel>
<MainTitle size="small">{`${altTitle || title} Course`}</MainTitle>
{type && <p className={cx('hero-subtitle')}>{type}</p>}
<DateLang startDate={date} language={language} mode={mode} withMargin />
<DateLang
startDate={date}
registrationEndDate={registrationEndDate}
language={language}
mode={mode}
withMargin
/>
<LinkCustom href={enroll} variant="secondary" external>
{heroCourseLocalized[lang].linkLabel}
</LinkCustom>
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/school-menu/school-menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('SchoolMenu', () => {
<SchoolMenu courses={mockedCourses} heading="all courses" />,
);

const descriptions = container.getElementsByTagName('small');
const descriptions = container.getElementsByClassName('description');

expect(descriptions).toHaveLength(6);
expect(descriptions[0]).toHaveTextContent(/tbd/i);
Expand Down
19 changes: 15 additions & 4 deletions src/widgets/school-menu/ui/school-item/school-item.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Image from 'next/image';
import Link from 'next/link';
import { GenericItemProps } from '../school-list/school-list';
import { COURSE_STALE_AFTER_DAYS } from '@/core/const';
import type { Course } from '@/entities/course';
import { getCourseDate } from '@/shared/helpers/getCourseDate';
import { DateStart } from '@/shared/ui/date-start';
import { MentorshipCourse } from 'data';

interface SchoolItemProps {
Expand All @@ -12,13 +11,25 @@ interface SchoolItemProps {
}

export const SchoolItem = ({ item, color }: SchoolItemProps) => {
const courseDate = 'startDate' in item && getCourseDate(item.startDate, COURSE_STALE_AFTER_DAYS);
const courseDate = 'startDate' in item && item.startDate;
const registrationEndDate = 'registrationEndDate' in item && item.registrationEndDate;
const descriptionText = 'description' in item ? item.description : courseDate;

const descriptionContent = (
<>
<span className={color}>{item.title}</span>
<small>{descriptionText}</small>
{courseDate && registrationEndDate
? (
<DateStart
className="description"
courseStartDate={courseDate}
registrationEndDate={registrationEndDate}
>
</DateStart>
)
: (
<small className="description">{descriptionText}</small>
)}
</>
);

Expand Down
2 changes: 1 addition & 1 deletion src/widgets/school-menu/ui/school-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
}
}

small {
.description {
font-size: 12px;
color: $color-gray-500;
}
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/upcoming-courses/ui/upcoming-courses.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import classNames from 'classnames/bind';
import Image from 'next/image';
import { COURSE_STALE_AFTER_DAYS, ROUTES } from '@/core/const';
import { ROUTES } from '@/core/const';
import type { Course } from '@/entities/course';
import { getCourses } from '@/entities/course/api/course-api';
import { CourseItem } from '@/entities/course/ui/course-item/course-item';
Expand All @@ -18,18 +18,18 @@ export const UpcomingCourses = async () => {
const courses = await getCourses();
const coursesData: Course[] = getActualData({
data: courses,
staleAfter: COURSE_STALE_AFTER_DAYS,
filterStale: true,
});

const coursesContent = coursesData
.slice(0, Math.min(coursesData.length, maxUpcomingCoursesQuantity))
.map(({ title, language, startDate, detailsUrl, iconSrc }) => {
.map(({ title, language, startDate, registrationEndDate, detailsUrl, iconSrc }) => {
return (
<CourseItem
title={title}
language={language}
startDate={startDate}
registrationEndDate={registrationEndDate}
detailsUrl={detailsUrl}
iconSrc={iconSrc}
key={title}
Expand Down

0 comments on commit 15835e5

Please sign in to comment.