Skip to content

Commit e37faff

Browse files
ahmaxedjdwilkin4
andauthored
feat(learn): add catalog (freeCodeCamp#65596)
Co-authored-by: jdwilkin4 <jwilkin4@hotmail.com>
1 parent 0ed5f0d commit e37faff

132 files changed

Lines changed: 4307 additions & 1288 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/i18n/locales.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {
1010
LangCodes
1111
} from '@freecodecamp/shared/config/i18n';
1212
import {
13-
catalogSuperBlocks,
14-
SuperBlocks
13+
SuperBlocks,
14+
superBlockStages,
15+
SuperBlockStage
1516
} from '@freecodecamp/shared/config/curriculum';
1617
import intro from './locales/english/intro.json';
1718

@@ -85,13 +86,14 @@ describe('Locale tests:', () => {
8586
describe('Intro file structure tests:', () => {
8687
const typedIntro = intro as unknown as Intro;
8788
const superblocks = Object.values(SuperBlocks);
89+
const catalogSuperBlocks = superBlockStages[SuperBlockStage.Catalog];
8890
for (const superBlock of superblocks) {
8991
test(`superBlock ${superBlock} has required properties`, () => {
9092
expect(typeof typedIntro[superBlock].title).toBe('string');
9193

9294
// catalog superblocks should have a summary
9395
if (catalogSuperBlocks.includes(superBlock)) {
94-
expect(typedIntro[superBlock].intro).toBeInstanceOf(Array);
96+
expect(typedIntro[superBlock].summary).toBeInstanceOf(Array);
9597
}
9698

9799
expect(typedIntro[superBlock].intro).toBeInstanceOf(Array);

client/i18n/locales/english/intro.json

Lines changed: 1237 additions & 10 deletions
Large diffs are not rendered by default.

client/i18n/locales/english/translations.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@
204204
],
205205
"cta": "Start Learning Now (it's free)"
206206
},
207+
"catalog": {
208+
"heading": "Explore Course Catalog",
209+
"seeAll": "See All Courses"
210+
},
207211
"certification-heading": "Earn free verified certifications in:",
208212
"core-certs-heading": "Recommended curriculum:",
209213
"learn-english-heading": "Learn English for Developers:",
@@ -710,7 +714,8 @@
710714
"exam": "Exam",
711715
"warm-up": "Warm-up",
712716
"learn": "Learn",
713-
"practice": "Practice"
717+
"practice": "Practice",
718+
"video": "Video"
714719
},
715720
"archive": {
716721
"title": "Archived Coursework",
@@ -1257,6 +1262,7 @@
12571262
"college-algebra-with-python-v8-cert": "College Algebra with Python Certification",
12581263
"foundational-c-sharp-with-microsoft": "Foundational C# with Microsoft",
12591264
"foundational-c-sharp-with-microsoft-cert": "Foundational C# with Microsoft Certification",
1265+
"learn-python-for-beginners": "Learn Python for Beginners",
12601266
"a2-english-for-developers": "A2 English for Developers",
12611267
"a2-english-for-developers-cert": "A2 English for Developers Certification (Beta)",
12621268
"b1-english-for-developers": "B1 English for Developers",
@@ -1429,6 +1435,21 @@
14291435
"beginner": "Beginner",
14301436
"intermediate": "Intermediate",
14311437
"advanced": "Advanced"
1438+
},
1439+
"duration": "{{duration}} hours",
1440+
"no-results": "No courses found. Try adjusting your filters to see more results.",
1441+
"topic": {
1442+
"html": "HTML",
1443+
"css": "CSS",
1444+
"js": "JavaScript",
1445+
"react": "React",
1446+
"python": "Python",
1447+
"data-analysis": "Data Analysis",
1448+
"machine-learning": "Machine Learning",
1449+
"d3": "D3",
1450+
"api": "APIs",
1451+
"information-security": "Information Security",
1452+
"computer-fundamentals": "Computer Fundamentals"
14321453
}
14331454
}
14341455
}

client/src/assets/superblock-icon.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,31 @@ const iconMap = {
5656
[SuperBlocks.SemanticHtml]: Code,
5757
[SuperBlocks.FullStackOpen]: Code,
5858
[SuperBlocks.DevPlayground]: Code,
59+
[SuperBlocks.HtmlFormsAndTables]: ResponsiveDesign,
60+
[SuperBlocks.LabSurveyForm]: Code,
61+
[SuperBlocks.HtmlAndAccessibility]: ResponsiveDesign,
62+
[SuperBlocks.ComputerBasics]: Code,
63+
[SuperBlocks.BasicCss]: Code,
64+
[SuperBlocks.DesignForDevelopers]: Code,
65+
[SuperBlocks.AbsoluteAndRelativeUnits]: Code,
66+
[SuperBlocks.PseudoClassesAndElements]: Code,
67+
[SuperBlocks.CssColors]: Code,
68+
[SuperBlocks.StylingForms]: Code,
69+
[SuperBlocks.CssBoxModel]: Code,
70+
[SuperBlocks.CssFlexbox]: Code,
71+
[SuperBlocks.LabPageOfPlayingCards]: Code,
72+
[SuperBlocks.CssTypography]: Code,
73+
[SuperBlocks.CssAndAccessibility]: ResponsiveDesign,
74+
[SuperBlocks.CssPositioning]: Code,
75+
[SuperBlocks.AttributeSelectors]: Code,
76+
[SuperBlocks.LabBookInventoryApp]: Code,
77+
[SuperBlocks.ResponsiveDesign]: ResponsiveDesign,
78+
[SuperBlocks.LabTechnicalDocumentationPage]: Code,
79+
[SuperBlocks.CssVariables]: Code,
80+
[SuperBlocks.CssGrid]: Code,
81+
[SuperBlocks.LabProductLandingPage]: Code,
82+
[SuperBlocks.CssAnimations]: Code,
83+
[SuperBlocks.LearnPythonForBeginners]: PythonIcon,
5984
[SuperBlocks.RespWebDesignV9]: ResponsiveDesign,
6085
[SuperBlocks.JsV9]: JavaScriptIcon,
6186
[SuperBlocks.FrontEndDevLibsV9]: ReactIcon,

client/src/components/Map/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ function Map({ forLanding = false }: MapProps) {
9494
showUpcomingChanges
9595
})
9696
// remove legacy superblocks from main maps - shown in archive map only
97-
.filter(stage => stage !== SuperBlockStage.Legacy)
97+
.filter(
98+
stage =>
99+
stage !== SuperBlockStage.Legacy &&
100+
stage !== SuperBlockStage.Catalog
101+
)
98102
.map(stage => {
99103
const superblocks = superBlockStages[stage];
100104
if (superblocks.length === 0) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3+
import { faClock, faStairs } from '@fortawesome/free-solid-svg-icons';
4+
import { useTranslation } from 'react-i18next';
5+
import { Link } from './helpers';
6+
7+
interface CatalogItemProps {
8+
superBlock: string;
9+
level: string;
10+
hours: number;
11+
topic: string;
12+
showAllSummaries?: boolean;
13+
}
14+
15+
const CatalogItem: React.FC<CatalogItemProps> = ({
16+
superBlock,
17+
level,
18+
hours,
19+
topic,
20+
showAllSummaries = false
21+
}) => {
22+
const { t } = useTranslation();
23+
24+
const { title, summary } = t(`intro:${superBlock}`, {
25+
returnObjects: true
26+
}) as {
27+
title: string;
28+
summary: string[];
29+
};
30+
31+
return (
32+
<Link to={`/learn/${superBlock}`} key={superBlock} className='catalog-item'>
33+
<div className='catalog-item-top'>
34+
<div className={`block-label block-label-${topic}`}>
35+
{t(`curriculum.catalog.topic.${topic}`)}
36+
</div>
37+
<h3>{title}</h3>
38+
{showAllSummaries ? (
39+
summary.map((text, i) => <p key={i}>{text}</p>)
40+
) : summary && summary.length > 0 ? (
41+
<p>{summary[0]}</p>
42+
) : null}
43+
</div>
44+
<div className='catalog-item-bottom'>
45+
<div>
46+
<FontAwesomeIcon icon={faStairs} />
47+
&nbsp; {t(`curriculum.catalog.levels.${level}`)}
48+
</div>
49+
<div>
50+
<FontAwesomeIcon icon={faClock} />
51+
&nbsp;{' '}
52+
{showAllSummaries
53+
? t('curriculum.catalog.duration', { duration: hours })
54+
: `${hours} hours`}
55+
</div>
56+
</div>
57+
</Link>
58+
);
59+
};
60+
61+
export default CatalogItem;

client/src/components/landing/components/faq.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const Faq = (): JSX.Element => {
2222
xs={12}
2323
className='faq-section'
2424
>
25+
<Spacer size='l' />
2526
<h2 className='big-heading'>{t('landing.faq')}</h2>
2627
<Spacer size='xs' />
2728
{faqItems.map((faq, i) => (
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React, { useMemo } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { Col, Row, Container } from '@freecodecamp/ui';
4+
import { Link } from '../../helpers';
5+
import { catalog } from '@freecodecamp/shared/config/catalog';
6+
import { SuperBlocks } from '@freecodecamp/shared/config/curriculum';
7+
import CatalogItem from '../../catalog-item';
8+
9+
import '../landing.css';
10+
import LinkButton from '../../../assets/icons/link-button';
11+
12+
const LandingCatalog = (): JSX.Element => {
13+
const { t } = useTranslation();
14+
15+
const featuredCourses = useMemo(() => {
16+
// Get featured courses: Learn Python for Beginners, Computer Basics, Basic HTML
17+
const featuredSuperBlocks = [
18+
SuperBlocks.LearnPythonForBeginners,
19+
SuperBlocks.ComputerBasics,
20+
SuperBlocks.BasicHtml
21+
];
22+
const courses = catalog.filter(course =>
23+
featuredSuperBlocks.includes(course.superBlock)
24+
);
25+
// Sort by the order in featuredSuperBlocks
26+
return courses.sort(
27+
(a, b) =>
28+
featuredSuperBlocks.indexOf(a.superBlock) -
29+
featuredSuperBlocks.indexOf(b.superBlock)
30+
);
31+
}, []);
32+
33+
return (
34+
<div className='landing-catalog landing-catalog-container'>
35+
<Container>
36+
<Row>
37+
<Col xs={12}>
38+
<h2 className='big-heading text-center'>
39+
{t('landing.catalog.heading')}
40+
</h2>
41+
</Col>
42+
</Row>
43+
</Container>
44+
<Container fluid={true} className='landing-catalog-container'>
45+
<Col md={10} mdOffset={1} sm={12} xs={12}>
46+
<section className='landing-catalog-wrap'>
47+
{featuredCourses.map(course => {
48+
const { superBlock, level, hours, topic } = course;
49+
50+
return (
51+
<CatalogItem
52+
key={superBlock}
53+
superBlock={superBlock}
54+
level={level}
55+
hours={hours}
56+
topic={topic}
57+
showAllSummaries={false}
58+
/>
59+
);
60+
})}
61+
<Link to='/catalog' className='catalog-item catalog-item-see-all'>
62+
<h3>{t('landing.catalog.seeAll')}</h3>
63+
<LinkButton />
64+
</Link>
65+
</section>
66+
</Col>
67+
</Container>
68+
</div>
69+
);
70+
};
71+
72+
LandingCatalog.displayName = 'LandingCatalog';
73+
export default LandingCatalog;

client/src/components/landing/landing.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,28 @@ figcaption.caption {
455455
font-weight: normal;
456456
text-align: center;
457457
}
458+
459+
/* Landing Catalog Styles */
460+
.landing-catalog-container {
461+
background-color: var(--primary-background);
462+
padding-top: 3rem;
463+
padding-bottom: 3rem;
464+
}
465+
466+
.landing-catalog-wrap {
467+
display: grid;
468+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
469+
width: 100%;
470+
gap: 2rem;
471+
}
472+
473+
.landing-catalog .catalog-item.catalog-item-see-all {
474+
display: flex;
475+
align-items: center;
476+
justify-content: center;
477+
min-height: 280px;
478+
text-align: center;
479+
fill: var(--secondary-color);
480+
flex-direction: row;
481+
gap: 10px;
482+
}

client/src/pages/catalog.css

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,83 @@
11
.catalog-wrap {
2-
display: flex;
2+
display: grid;
3+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
4+
width: 100%;
35
gap: 2rem;
4-
justify-content: space-evenly;
6+
}
7+
8+
.catalog-filters {
9+
display: flex;
10+
gap: 1rem;
11+
justify-content: flex-start;
512
flex-wrap: wrap;
13+
margin-bottom: 1rem;
14+
}
15+
16+
.catalog-filters .dropdown {
17+
min-width: 250px;
18+
}
19+
20+
.catalog-filters .dropdown-menu {
21+
max-height: 300px;
22+
overflow-y: auto;
23+
}
24+
25+
.catalog-filters .dropdown-item {
26+
cursor: pointer;
27+
display: flex;
28+
align-items: center;
29+
gap: 0.5rem;
30+
padding: 0.5rem 1rem;
31+
}
32+
33+
.catalog-filters button {
34+
display: flex;
35+
width: 100%;
36+
align-items: center;
37+
}
38+
39+
.catalog-filters .dropdown-menu .dropdown-item input[type='checkbox'] {
40+
flex-shrink: 0;
41+
}
42+
43+
.filter-checkbox {
44+
cursor: pointer;
45+
width: 18px;
46+
height: 18px;
47+
margin: 0 0.5rem 0 0;
48+
vertical-align: middle;
49+
flex-shrink: 0;
50+
}
51+
52+
.catalog-item-top {
53+
display: flex;
54+
flex-direction: column;
55+
gap: 10px;
656
}
757

858
.catalog-item {
59+
text-decoration: none;
960
padding: 1rem;
1061
background-color: var(--primary-background);
11-
width: 400px;
12-
min-height: 300px;
1362
display: flex;
1463
flex-direction: column;
1564
justify-content: space-between;
65+
border: 1px solid var(--background-quaternary);
66+
max-width: 400px;
67+
}
68+
69+
.catalog-item h3 {
70+
margin: 0;
1671
}
1772

1873
.catalog-item-bottom {
1974
display: flex;
2075
justify-content: space-between;
2176
align-items: flex-end;
77+
color: var(--quaternary-color);
78+
font-size: 0.9rem;
79+
}
80+
81+
.catalog-item p {
82+
margin-bottom: 0.9rem;
2283
}

0 commit comments

Comments
 (0)