Skip to content

Commit

Permalink
feat: update recommendations page design (#1036)
Browse files Browse the repository at this point in the history
VAN-1598
  • Loading branch information
syedsajjadkazmii authored and zainab-amir committed Sep 7, 2023
1 parent 7ca02d1 commit adfe07d
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 121 deletions.
2 changes: 2 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const configuration = {
INFO_EMAIL: process.env.INFO_EMAIL || '',
ZENDESK_KEY: process.env.ZENDESK_KEY,
ZENDESK_LOGO_URL: process.env.ZENDESK_LOGO_URL,
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || '',
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || '',
};

export default configuration;
4 changes: 2 additions & 2 deletions src/recommendations/ProductCard/BaseCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const BaseCard = ({
productTypeCopy,
footer,
handleOnClick,
isLoading = false,
isLoading,
}) => (
<div className="mr-4 recommendation-card" key={`container-${uuid}`}>
<div className="recommendation-card" key={`container-${uuid}`}>
<Hyperlink
target="_blank"
className="card-box"
Expand Down
4 changes: 4 additions & 0 deletions src/recommendations/ProductCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ProductCard = ({
product,
userId,
position,
isLoading,
}) => {
const { formatMessage } = useIntl();

Expand Down Expand Up @@ -82,6 +83,7 @@ const ProductCard = ({
productTypeCopy={productTypeCopy}
productType={productType}
variant={variant}
isLoading={isLoading}
footer={(
<Footer
quickFacts={product.degree?.quickFacts}
Expand All @@ -105,8 +107,10 @@ ProductCard.propTypes = {
]).isRequired,
userId: PropTypes.number.isRequired,
position: PropTypes.number.isRequired,
isLoading: PropTypes.bool,
};

ProductCard.defaultProps = {
isLoading: false,
};
export default ProductCard;
20 changes: 11 additions & 9 deletions src/recommendations/RecommendationsList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import PropTypes from 'prop-types';
import ProductCard from './ProductCard';

const RecommendationsList = (props) => {
const { recommendations, userId } = props;
const { recommendations, userId, isLoading } = props;

return (
<div className="d-flex recommendations-container__card-list">
<div className="d-flex flex-wrap mb-3 recommendations-container__card-list">
{
recommendations.map((recommendation, idx) => (
<span key={recommendation.uuid}>
<ProductCard
product={recommendation}
position={idx}
userId={userId}
/>
</span>
<ProductCard
key={recommendation.uuid}
product={recommendation}
position={idx}
userId={userId}
isLoading={isLoading}
/>
))
}
</div>
Expand All @@ -29,11 +29,13 @@ RecommendationsList.propTypes = {
uuid: PropTypes.string,
})),
userId: PropTypes.number,
isLoading: PropTypes.bool,
};

RecommendationsList.defaultProps = {
recommendations: [],
userId: null,
isLoading: false,
};

export default RecommendationsList;
95 changes: 50 additions & 45 deletions src/recommendations/RecommendationsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import React, { useEffect } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Container, Hyperlink, Image, StatefulButton, Tab, Tabs,
breakpoints,
Container,
Hyperlink,
Image, Skeleton,
StatefulButton,
useMediaQuery,
} from '@edx/paragon';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { useLocation } from 'react-router-dom';

import { EDUCATION_LEVEL_MAPPING, POPULAR, TRENDING } from './data/constants';
import { EDUCATION_LEVEL_MAPPING, POPULAR } from './data/constants';
import useRecommendations from './data/hooks/useRecommendations';
import messages from './messages';
import RecommendationsList from './RecommendationsList';
import RecommendationsLargeLayout from './RecommendationsPageLayouts/LargeLayout';
import RecommendationsSmallLayout from './RecommendationsPageLayouts/SmallLayout';
import { trackRecommendationsViewed } from './track';
import { DEFAULT_REDIRECT_URL } from '../data/constants';

Expand All @@ -23,8 +29,10 @@ const RecommendationsPage = ({}) => {
const educationLevel = EDUCATION_LEVEL_MAPPING[location.state?.educationLevel];
const userId = location.state?.userId;

const isExtraSmall = useMediaQuery({ maxWidth: breakpoints.extraSmall.maxWidth - 1 });

const {
popularProducts, trendingProducts, isLoading,
algoliaRecommendations, popularProducts, trendingProducts, isLoading,
} = useRecommendations(educationLevel);

const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
Expand Down Expand Up @@ -56,62 +64,59 @@ const RecommendationsPage = ({}) => {
handleRedirection();
}

const handleOnSelect = (tabKey) => {
const recommendations = tabKey === POPULAR ? popularProducts : trendingProducts;
trackRecommendationsViewed(recommendations, tabKey, false, userId);
};

return (
<>
<Helmet>
<title>{formatMessage(messages['recommendation.page.title'],
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<div className="d-flex flex-column vh-100 bg-light-200">
<div className="d-flex flex-column bg-light-200">
<div className="mb-2">
<div className="col-md-12 small-screen-top-stripe medium-screen-top-stripe extra-large-screen-top-stripe" />
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
</Hyperlink>
</div>
<div className="d-flex flex-column align-items-center justify-content-center flex-grow-1 p-1">
<Container id="course-recommendations" size="lg" className="recommendations-container">
<h2 className="text-sm-center mb-4 text-left recommendations-container__heading">
{formatMessage(messages['recommendation.page.heading'])}
</h2>
<Tabs
variant="tabs"
defaultActiveKey={POPULAR}
id="recommendations-selection"
onSelect={handleOnSelect}
>
<Tab tabClassName="mb-3" eventKey={POPULAR} title={formatMessage(messages['recommendation.option.popular'])}>
<RecommendationsList
recommendations={popularProducts}
userId={userId}
/>
</Tab>
<Tab tabClassName="mb-3" eventKey={TRENDING} title={formatMessage(messages['recommendation.option.trending'])}>
<RecommendationsList
recommendations={trendingProducts}
userId={userId}
/>
</Tab>
</Tabs>
</Container>
<div className="text-center">
<StatefulButton
className="font-weight-500"
type="submit"
variant="brand"
labels={{
default: formatMessage(messages['recommendation.skip.button']),
}}
onClick={handleSkip}
<Container
id="course-recommendations"
size="lg"
className="pr-4 pl-4 mt-3 mt-md-4 mb-4.5 mb-sm-5 mb-md-6 mw-lg"
>
{isExtraSmall ? (
<RecommendationsSmallLayout
userId={userId}
isLoading={isLoading}
personalizedRecommendations={algoliaRecommendations}
trendingProducts={trendingProducts}
popularProducts={popularProducts}
/>
) : (
<RecommendationsLargeLayout
userId={userId}
isLoading={isLoading}
personalizedRecommendations={algoliaRecommendations}
trendingProducts={trendingProducts}
popularProducts={popularProducts}
/>
)}
<div className="mt-3 mt-sm-4.5 text-center">
{isLoading && (
<Skeleton className="skip-btn__skeleton" />
)}
{!isLoading && (
<StatefulButton
className="font-weight-500"
type="submit"
variant="outline-brand"
labels={{
default: formatMessage(messages['recommendation.skip.button']),
}}
onClick={handleSkip}
/>
)}
</div>
</div>
</Container>
</div>
</>
);
Expand Down
95 changes: 95 additions & 0 deletions src/recommendations/RecommendationsPageLayouts/LargeLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';

import { useIntl } from '@edx/frontend-platform/i18n';
import { Skeleton } from '@edx/paragon';
import PropTypes from 'prop-types';

import loadingCoursesPlaceholders from '../data/loadingCoursesPlaceholders';
import messages from '../messages';
import RecommendationsList from '../RecommendationsList';

const RecommendationsLargeLayout = (props) => {
const {
userId,
isLoading,
personalizedRecommendations,
trendingProducts,
popularProducts,
} = props;
const { formatMessage } = useIntl();

if (isLoading) {
return (
<>
<Skeleton className="recommendations-heading__skeleton" />
<Skeleton className="recommendations-subheading__skeleton" />
<RecommendationsList
recommendations={loadingCoursesPlaceholders}
userId={userId}
isLoading
/>
<Skeleton className="recommendations-subheading__skeleton" />
<RecommendationsList
recommendations={loadingCoursesPlaceholders}
userId={userId}
isLoading
/>
<Skeleton className="recommendations-subheading__skeleton" />
<RecommendationsList
recommendations={loadingCoursesPlaceholders}
userId={userId}
isLoading
/>
</>
);
}
return (
<>
<h2 className="text-sm-center mb-5 mb-sm-4.5 text-left recommendations-container__heading">
{formatMessage(messages['recommendation.page.heading'])}
</h2>
{personalizedRecommendations.length > 0 && (
<>
<h3 className="text-sm-center mb-3 text-left">
{formatMessage(messages['recommendation.option.recommended.for.you'])}
</h3>
<RecommendationsList
recommendations={personalizedRecommendations}
userId={userId}
/>
</>
)}
<h3 className="text-sm-center mb-3 text-left">
{formatMessage(messages['recommendation.option.popular'])}
</h3>
<RecommendationsList
recommendations={popularProducts}
userId={userId}
/>
<h3 className="text-sm-center mb-3 text-left">
{formatMessage(messages['recommendation.option.trending'])}
</h3>
<RecommendationsList
recommendations={trendingProducts}
userId={userId}
/>
</>
);
};

RecommendationsLargeLayout.propTypes = {
userId: PropTypes.number.isRequired,
isLoading: PropTypes.bool,
personalizedRecommendations: PropTypes.arrayOf(PropTypes.shape({})),
trendingProducts: PropTypes.arrayOf(PropTypes.shape({})),
popularProducts: PropTypes.arrayOf(PropTypes.shape({})),
};

RecommendationsLargeLayout.defaultProps = {
isLoading: true,
personalizedRecommendations: [],
trendingProducts: [],
popularProducts: [],
};

export default RecommendationsLargeLayout;
Loading

0 comments on commit adfe07d

Please sign in to comment.