Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/generic/course-card/CourseCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import messages from './messages';

describe('CourseCard', () => {
const renderComponent = (course = mockCourseResponse) => render(
<CourseCard course={course} />,
<CourseCard original={course} />,
);

it('renders course information correctly', () => {
Expand Down
22 changes: 11 additions & 11 deletions src/generic/course-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,32 @@ import noOrgImg from '../../assets/no-org-image.svg';

// TODO: Determine the final design for the course Card component.
// Issue: https://github.com/openedx/frontend-app-catalog/issues/10
export const CourseCard = ({ course }: CourseCardProps) => {
export const CourseCard = ({ original }: CourseCardProps) => {
const intl = useIntl();
const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.small.maxWidth });

const formattedDate = course?.data?.start
? intl.formatDate(new Date(course.data.start), DATE_FORMAT_OPTIONS)
const formattedDate = original?.data?.start
? intl.formatDate(new Date(original.data.start), DATE_FORMAT_OPTIONS)
: '';

return (
<Card
as={Link}
to={`/courses/${course.id}/about`}
to={`/courses/${original.id}/about`}
className={`course-card ${isExtraSmall ? 'w-100' : 'course-card-desktop'}`}
isClickable
>
<Card.ImageCap
src={getFullImageUrl(course.data.imageUrl)}
src={getFullImageUrl(original.data.imageUrl)}
fallbackSrc={noCourseImg}
srcAlt={course.data.content.displayName}
logoSrc={course.data.orgImg ? getFullImageUrl(course.data.orgImg) : undefined}
fallbackLogoSrc={!course.data.orgImg && noOrgImg}
logoAlt={course.data.org}
srcAlt={original.data.content.displayName}
logoSrc={original.data.orgImg ? getFullImageUrl(original.data.orgImg) : undefined}
fallbackLogoSrc={!original.data.orgImg && noOrgImg}
logoAlt={original.data.org}
/>
<Card.Section>
<h3 className="m-0">{course.data.content.displayName}</h3>
<p className="m-0">{course.data.org}</p>
<h3 className="m-0">{original.data.content.displayName}</h3>
<p className="m-0">{original.data.org}</p>
{formattedDate && (
<span>
{intl.formatMessage(messages.startDate, {
Expand Down
2 changes: 1 addition & 1 deletion src/generic/course-card/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export interface Course {
}

export interface CourseCardProps {
course: Course;
original: Course;
}
6 changes: 4 additions & 2 deletions src/generic/sub-header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import classNames from 'classnames';

import { SubHeaderProps } from './types';

export const SubHeader = ({ title }: SubHeaderProps) => (
<header className="mb-5 d-flex justify-content-between">
export const SubHeader = ({ title, className }: SubHeaderProps) => (
<header className={classNames('mb-5 d-flex justify-content-between', className)}>
<h1 className="mb-0">{title}</h1>
</header>
);
1 change: 1 addition & 0 deletions src/generic/sub-header/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface SubHeaderProps {
title: string;
className?: string;
}
12 changes: 12 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";

.pgn__data-table-layout-sidebar {
min-width: 386px;
}

.pgn__data-table-layout-wrapper {
margin-bottom: 20px;
}

.pgn__searchfield {
width: 386px;
}
108 changes: 73 additions & 35 deletions src/сatalog/CatalogPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import {
CardGrid, Container, Layout, Alert,
Container, Alert, SearchField, DataTable, TextFilter,
CardView, CheckboxFilter, useMediaQuery, breakpoints,
} from '@openedx/paragon';
import { ErrorPage } from '@edx/frontend-platform/react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import classNames from 'classnames';

import {
AlertNotification, CourseCard, Loading, SubHeader,
AlertNotification,
CourseCard,
Loading,
SubHeader,
} from '../generic';
import { useCourseDiscovery } from './data/hooks';
import { transformResultsForTable } from './utils';
import messages from './messages';
import { DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from './constants';

const GRID_LAYOUT = { xl: [{ span: 9 }, { span: 3 }] };
const enableCourseDiscovery = false;

const CatalogPage = () => {
const intl = useIntl();
Expand All @@ -20,6 +27,7 @@ const CatalogPage = () => {
isLoading,
isError,
} = useCourseDiscovery();
const isMedium = useMediaQuery({ maxWidth: breakpoints.large.maxWidth });

if (isLoading) {
return (
Expand All @@ -45,39 +53,69 @@ const CatalogPage = () => {

return (
<Container className="container-xl pt-5.5">
<SubHeader title={intl.formatMessage(messages.totalCoursesHeading, {
totalCourses,
})}
<SubHeader
title={intl.formatMessage(messages.exploreCourses)}
className={classNames({ 'mx-2.5': isMedium })}
/>
<Layout {...GRID_LAYOUT}>
<Layout.Element>
{totalCourses === 0 ? (
<AlertNotification
title={intl.formatMessage(messages.noCoursesAvailable)}
message={intl.formatMessage(messages.noCoursesAvailableMessage)}
/>
) : (
<CardGrid
hasEqualColumnHeights
className="mb-6"
>
{courseData?.results?.map(course => (
<CourseCard
key={course.id}
course={course}
/>
))}
</CardGrid>
)}
</Layout.Element>
<Layout.Element>
{totalCourses > 0 && (
<aside className="sidebar-wrapper">
{/* TODO: Implement sidebar functionality with filters and additional course information */}
</aside>
)}
</Layout.Element>
</Layout>
{totalCourses > 0 ? (
<>
<SearchField
key=""
className={classNames({
'w-auto mx-2.5 mb-0': isMedium,
'mb-4': !isMedium,
})}
value=""
onSubmit={() => {}}
onClear={() => {}}
placeholder={intl.formatMessage(messages.searchPlaceholder)}
/>
<DataTable
isLoading={isLoading}
showFiltersInSidebar={!isMedium}
isFilterable={enableCourseDiscovery}
isSortable
isPaginated
defaultColumnValues={{ Filter: TextFilter }}
itemCount={totalCourses}
initialState={{ pageSize: DEFAULT_PAGE_SIZE, pageIndex: DEFAULT_PAGE_INDEX }}
data={transformResultsForTable(courseData?.results)}
columns={[
{
Header: 'Language',
accessor: 'language',
Filter: CheckboxFilter,
filter: 'includesValue',
filterChoices: [{
name: 'English',
number: 2,
value: 'English',
},
{
name: 'Ukrainian',
number: 2,
value: 'Ukrainian',
},
{
name: 'Spanish',
number: 1,
value: 'Spanish',
}],
},
]}
>
<DataTable.TableControlBar />
<CardView CardComponent={CourseCard} />
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFound)} />
<DataTable.TableFooter />
</DataTable>
</>
) : (
<AlertNotification
title={intl.formatMessage(messages.noCoursesAvailable)}
message={intl.formatMessage(messages.noCoursesAvailableMessage)}
/>
)}
</Container>
);
};
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/сatalog/data/__tests__/courseDiscovery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

import { renderHook, waitFor } from '../../../setupTest';
import { mockCourseDiscoveryResponse } from '../../__mocks__';
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from '../../constants';
import { fetchCourseDiscovery } from '../api';
import { useCourseDiscovery } from '../hooks';
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from '../constants';
import { getCourseDiscoveryUrl } from '../urls';

jest.mock('@edx/frontend-platform/auth', () => ({
Expand Down
2 changes: 1 addition & 1 deletion src/сatalog/data/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from './constants';
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from '../constants';
import { getCourseDiscoveryUrl } from './urls';

import { CourseDiscoveryResponse } from './types';
Expand Down
2 changes: 1 addition & 1 deletion src/сatalog/data/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import { fetchCourseDiscovery } from './api';
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from './constants';
import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_INDEX } from '../constants';
import { CourseDiscoveryResponse } from './types';

/**
Expand Down
4 changes: 3 additions & 1 deletion src/сatalog/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ export interface CourseDiscoveryResponse {
count: number;
results: {
id: string;
title: string;
index: string;
type: string;
data: {
id: string;
course: string;
start: string;
imageUrl: string;
org: string;
orgImageUrl?: string;
content: {
displayName: string;
overview?: string;
Expand Down
15 changes: 15 additions & 0 deletions src/сatalog/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ const messages = defineMessages({
defaultMessage: 'Viewing {totalCourses} courses',
description: 'Total courses heading',
},
searchPlaceholder: {
id: 'category.catalog.search-placeholder',
defaultMessage: 'Search for a course',
description: 'Search placeholder',
},
exploreCourses: {
id: 'category.catalog.explore-courses',
defaultMessage: 'Explore courses',
description: 'Explore courses',
},
noResultsFound: {
id: 'category.catalog.no-results-found',
defaultMessage: 'No results found',
description: 'No results found',
},
});

export default messages;
12 changes: 12 additions & 0 deletions src/сatalog/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CourseDiscoveryResponse } from './data/types';

export interface TransformedCourseItem {
id: string;
famous_for: string;
language: string;
modes: string[];
org: string;
data: CourseDiscoveryResponse['results'][0]['data'];
index?: string;
type?: string;
}
22 changes: 22 additions & 0 deletions src/сatalog/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { TransformedCourseItem } from './types';
import type { CourseDiscoveryResponse } from './data/types';

/**
* Transforms course discovery results into a format suitable for DataTable display.
*/
export const transformResultsForTable = (results: CourseDiscoveryResponse['results'] | undefined): TransformedCourseItem[] => {
if (!results?.length) {
return [];
}

return results.map(item => ({
id: item.id,
famous_for: item.data.content.displayName,
language: item.data.language,
modes: item.data.modes,
org: item.data.org,
data: item.data,
index: item.index,
type: item.type,
}));
};