Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

646-fix: Stale courses date #647

Merged
merged 5 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/visual-testing-on-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ jobs:
PERCY_PULL_REQUEST: ${{ github.event.issue.number }}
PERCY_COMMIT: ${{ steps.get_pr.outputs.commit_sha }}
PERCY_BRANCH: ${{ steps.get_pr.outputs.branch_name }}
API_URL: ${{ secrets.API_URL }}
1 change: 1 addition & 0 deletions .github/workflows/visual-testing-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ jobs:
- run: npm run test:visual
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
API_URL: ${{ secrets.API_URL }}
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
Loading
Loading