diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index 2c4a5dfbaa5..f9267403944 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -214,6 +214,7 @@
"next-heading": "Try our beta curriculum:",
"upcoming-heading": "Upcoming curriculum:",
"catalog-heading": "Explore our Catalog:",
+ "archive-link": "Looking for older coursework? Check out <0>our archive page0>.",
"faq": "Frequently asked questions:",
"faqs": [
{
@@ -662,6 +663,10 @@
"warm-up": "Warm-up",
"learn": "Learn",
"practice": "Practice"
+ },
+ "archive": {
+ "title": "Archived Coursework",
+ "content-not-updated": "<0>Warning:0> The content in this section is not being updated, but is still available for you to further your learning. We recommend trying <1>our current curriculum1>."
}
},
"donate": {
diff --git a/client/src/components/Map/index.tsx b/client/src/components/Map/index.tsx
index 024226a4e2a..b68b52d60a2 100644
--- a/client/src/components/Map/index.tsx
+++ b/client/src/components/Map/index.tsx
@@ -1,17 +1,18 @@
import i18next from 'i18next';
import React, { Fragment } from 'react';
import { Spacer } from '@freecodecamp/ui';
-import { useTranslation } from 'react-i18next';
+import { useTranslation, Trans } from 'react-i18next';
import {
type SuperBlocks,
SuperBlockStage,
getStageOrder,
- superBlockStages
+ superBlockStages,
+ archivedSuperBlocks
} from '../../../../shared-dist/config/curriculum';
import { SuperBlockIcon } from '../../assets/superblock-icon';
import LinkButton from '../../assets/icons/link-button';
-import { ButtonLink } from '../helpers';
+import { ButtonLink, Link } from '../helpers';
import { showUpcomingChanges } from '../../../config/env.json';
import DailyCodingChallengeWidget from '../daily-coding-challenge/widget';
@@ -68,6 +69,20 @@ function MapLi({
);
}
+// used on /learn/archive
+export function ArchiveMap() {
+ return (
+
+
+ {archivedSuperBlocks.map(superblock => (
+
+ ))}
+
+
+ );
+}
+
+// used on /learn and landing page
function Map({ forLanding = false }: MapProps) {
const { t } = useTranslation();
@@ -75,39 +90,48 @@ function Map({ forLanding = false }: MapProps) {
{getStageOrder({
showUpcomingChanges
- }).map(stage => {
- const superblocks = superBlockStages[stage];
- if (superblocks.length === 0) {
- return null;
- }
+ })
+ // remove legacy superblocks from main maps - shown in archive map only
+ .filter(stage => stage !== SuperBlockStage.Legacy)
+ .map(stage => {
+ const superblocks = superBlockStages[stage];
+ if (superblocks.length === 0) {
+ return null;
+ }
- return (
-
- {
- /* Show the daily coding challenge before the "English" curriculum */
- stage === SuperBlockStage.English && (
- <>
-
-
- >
- )
- }
-
- {t(superBlockHeadings[stage])}
-
-
- {superblocks.map(superblock => (
-
- ))}
-
-
-
- );
- })}
+ return (
+
+ {
+ /* Show the daily coding challenge before the "English" curriculum */
+ stage === SuperBlockStage.English && (
+ <>
+
+
+ >
+ )
+ }
+
+ {t(superBlockHeadings[stage])}
+
+
+ {superblocks.map(superblock => (
+
+ ))}
+
+
+
+ );
+ })}
+
+
+
+ placeholder
+
+
);
}
diff --git a/client/src/components/Map/map.css b/client/src/components/Map/map.css
index c57ac074943..c1fe76ac1fa 100644
--- a/client/src/components/Map/map.css
+++ b/client/src/components/Map/map.css
@@ -8,6 +8,10 @@
padding: 0;
}
+.map-ui .archive-link {
+ font-size: 1.25rem;
+}
+
@media (max-width: 640px) {
.map-ui .block ul {
padding-inline-start: 6rem;
diff --git a/client/src/components/archived-warning/index.css b/client/src/components/archived-warning/index.css
new file mode 100644
index 00000000000..3441a875acf
--- /dev/null
+++ b/client/src/components/archived-warning/index.css
@@ -0,0 +1,8 @@
+.archived-warning {
+ margin-bottom: 0;
+ font-size: 1rem;
+}
+
+.archived-warning strong {
+ color: var(--blue-dark);
+}
diff --git a/client/src/components/archived-warning/index.tsx b/client/src/components/archived-warning/index.tsx
new file mode 100644
index 00000000000..0972aa3b8b9
--- /dev/null
+++ b/client/src/components/archived-warning/index.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { Trans } from 'react-i18next';
+import { Callout } from '@freecodecamp/ui';
+
+import { Link } from '../helpers';
+
+import './index.css';
+
+const ArchivedWarning = () => {
+ return (
+
+
+
+ placeholder
+ placeholder
+
+
+
+ );
+};
+
+export default ArchivedWarning;
diff --git a/client/src/pages/learn/archive.tsx b/client/src/pages/learn/archive.tsx
new file mode 100644
index 00000000000..f44ff50c027
--- /dev/null
+++ b/client/src/pages/learn/archive.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Container, Row, Col, Spacer } from '@freecodecamp/ui';
+import Helmet from 'react-helmet';
+import LearnLayout from '../../components/layouts/learn';
+
+import { ArchiveMap } from '../../components/Map';
+import CalendarIcon from '../../assets/icons/calendar';
+import ArchivedWarning from '../../components/archived-warning';
+
+const ArchivePage = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+ {t('learn.archive.title')}
+
+
+
+
+
+
+ {t('landing.legacy-curriculum-heading')}
+
+
+
+
+
+
+ );
+};
+
+export default ArchivePage;
diff --git a/client/src/templates/Introduction/components/legacy-links.tsx b/client/src/templates/Introduction/components/legacy-links.tsx
index 6ef7d93dc39..f50a38bf6ce 100644
--- a/client/src/templates/Introduction/components/legacy-links.tsx
+++ b/client/src/templates/Introduction/components/legacy-links.tsx
@@ -2,12 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Alert } from '@freecodecamp/ui';
import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
-import {
- isOldRespCert,
- isRelationalDbCert,
- isExamCert
-} from '../../../utils/is-a-cert';
-import { Link } from '../../../components/helpers';
+import { isRelationalDbCert, isExamCert } from '../../../utils/is-a-cert';
import { CodeAllyDown } from '../../../components/growth-book/codeally-down';
import envData from '../../../../config/env.json';
@@ -22,18 +17,7 @@ interface LegacyLinksProps {
function LegacyLinks({ superBlock }: LegacyLinksProps): JSX.Element {
const { t } = useTranslation();
- if (isOldRespCert(superBlock)) {
- return (
-
-
- {t('intro:misc-text.legacy-desc')}{' '}
-
- {t('intro:misc-text.legacy-go-back')}
-
-
-
- );
- } else if (isRelationalDbCert(superBlock)) {
+ if (isRelationalDbCert(superBlock)) {
return (
<>
diff --git a/client/src/templates/Introduction/components/super-block-intro.tsx b/client/src/templates/Introduction/components/super-block-intro.tsx
index d2419b1a62b..c9e30e641ee 100644
--- a/client/src/templates/Introduction/components/super-block-intro.tsx
+++ b/client/src/templates/Introduction/components/super-block-intro.tsx
@@ -4,7 +4,10 @@ import { useTranslation, Trans } from 'react-i18next';
import { Alert, Spacer, Container, Row, Col, Callout } from '@freecodecamp/ui';
import { ConnectedProps, connect } from 'react-redux';
import { useFeatureIsOn } from '@growthbook/growthbook-react';
-import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
+import {
+ archivedSuperBlocks,
+ SuperBlocks
+} from '../../../../../shared-dist/config/curriculum';
import { SuperBlockIcon } from '../../../assets/superblock-icon';
import { Link } from '../../../components/helpers';
import CapIcon from '../../../assets/icons/cap';
@@ -12,6 +15,7 @@ import DumbbellIcon from '../../../assets/icons/dumbbell';
import CommunityIcon from '../../../assets/icons/community';
import { CompletedChallenge } from '../../../redux/prop-types';
import { completedChallengesSelector } from '../../../redux/selectors';
+import ArchivedWarning from '../../../components/archived-warning';
interface SuperBlockIntroQueryData {
challengeNode: {
@@ -142,6 +146,8 @@ function SuperBlockIntro({
const introTopA = (
<>
+ {archivedSuperBlocks.includes(superBlock) && }
+
{i18nSuperBlock}
diff --git a/client/src/utils/is-a-cert.ts b/client/src/utils/is-a-cert.ts
index a047fea5f9f..c6992b5ef5d 100644
--- a/client/src/utils/is-a-cert.ts
+++ b/client/src/utils/is-a-cert.ts
@@ -1,9 +1,5 @@
import { SuperBlocks } from '../../../shared-dist/config/curriculum';
-export function isOldRespCert(superBlock: string): boolean {
- return superBlock === String(SuperBlocks.RespWebDesign);
-}
-
export function isRelationalDbCert(superBlock: string): boolean {
return superBlock === String(SuperBlocks.RelationalDb);
}
diff --git a/e2e/archive.spec.ts b/e2e/archive.spec.ts
new file mode 100644
index 00000000000..782b10d07b0
--- /dev/null
+++ b/e2e/archive.spec.ts
@@ -0,0 +1,44 @@
+import { expect, test } from '@playwright/test';
+import intro from '../client/i18n/locales/english/intro.json';
+import { SuperBlocks } from '../shared/config/curriculum';
+
+const archivedSuperBlocks = [
+ intro[SuperBlocks.RespWebDesignNew].title,
+ intro[SuperBlocks.JsAlgoDataStructNew].title,
+ intro[SuperBlocks.FrontEndDevLibs].title,
+ intro[SuperBlocks.DataVis].title,
+ intro[SuperBlocks.RelationalDb].title,
+ intro[SuperBlocks.BackEndDevApis].title,
+ intro[SuperBlocks.QualityAssurance].title,
+ intro[SuperBlocks.SciCompPy].title,
+ intro[SuperBlocks.DataAnalysisPy].title,
+ intro[SuperBlocks.InfoSec].title,
+ intro[SuperBlocks.MachineLearningPy].title,
+ intro[SuperBlocks.CollegeAlgebraPy].title,
+ intro[SuperBlocks.RespWebDesign].title,
+ intro[SuperBlocks.JsAlgoDataStruct].title,
+ intro[SuperBlocks.PythonForEverybody].title
+];
+
+test.describe('Archive Page', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/learn/archive');
+ });
+
+ test('Links to new curriculum', async ({ page }) => {
+ const newCurriculumLink = page.locator(
+ 'a[href="/learn/full-stack-developer"]'
+ );
+ await expect(newCurriculumLink).toBeVisible();
+ });
+
+ test('Links to all archived superblocks in order', async ({ page }) => {
+ const curriculumBtns = page.getByTestId('curriculum-map-button');
+ await expect(curriculumBtns).toHaveCount(archivedSuperBlocks.length);
+ for (let index = 0; index < archivedSuperBlocks.length; index++) {
+ const btn = curriculumBtns.nth(index);
+ const link = btn.getByRole('link', { name: archivedSuperBlocks[index] });
+ await expect(link).toBeVisible();
+ }
+ });
+});
diff --git a/e2e/landing.spec.ts b/e2e/landing.spec.ts
index 62385373d54..070469d5049 100644
--- a/e2e/landing.spec.ts
+++ b/e2e/landing.spec.ts
@@ -15,7 +15,7 @@ const landingPageElements = {
jobs: 'More than 100,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:'
} as const;
-const superBlocks = [
+const nonArchivedSuperBlocks = [
intro[SuperBlocks.FullStackDeveloper].title,
intro[SuperBlocks.A2English].title,
intro[SuperBlocks.B1English].title,
@@ -23,21 +23,6 @@ const superBlocks = [
intro[SuperBlocks.CodingInterviewPrep].title,
intro[SuperBlocks.ProjectEuler].title,
intro[SuperBlocks.RosettaCode].title,
- intro[SuperBlocks.RespWebDesignNew].title,
- intro[SuperBlocks.JsAlgoDataStructNew].title,
- intro[SuperBlocks.FrontEndDevLibs].title,
- intro[SuperBlocks.DataVis].title,
- intro[SuperBlocks.RelationalDb].title,
- intro[SuperBlocks.BackEndDevApis].title,
- intro[SuperBlocks.QualityAssurance].title,
- intro[SuperBlocks.SciCompPy].title,
- intro[SuperBlocks.DataAnalysisPy].title,
- intro[SuperBlocks.InfoSec].title,
- intro[SuperBlocks.MachineLearningPy].title,
- intro[SuperBlocks.CollegeAlgebraPy].title,
- intro[SuperBlocks.RespWebDesign].title,
- intro[SuperBlocks.JsAlgoDataStruct].title,
- intro[SuperBlocks.PythonForEverybody].title,
intro[SuperBlocks.FoundationalCSharp].title
];
@@ -209,16 +194,23 @@ test.describe('Landing Page', () => {
}
});
- test('Links to all superblocks in order', async ({ page }) => {
+ test('Links to all non-archived superblocks in order', async ({ page }) => {
const curriculumBtns = page.getByTestId(landingPageElements.curriculumBtns);
- await expect(curriculumBtns).toHaveCount(superBlocks.length);
- for (let index = 0; index < superBlocks.length; index++) {
+ await expect(curriculumBtns).toHaveCount(nonArchivedSuperBlocks.length);
+ for (let index = 0; index < nonArchivedSuperBlocks.length; index++) {
const btn = curriculumBtns.nth(index);
- const link = btn.getByRole('link', { name: superBlocks[index] });
+ const link = btn.getByRole('link', {
+ name: nonArchivedSuperBlocks[index]
+ });
await expect(link).toBeVisible();
}
});
+ test('Links to the archive page', async ({ page }) => {
+ const archiveLink = page.locator('a[href="/learn/archive"]');
+ await expect(archiveLink).toBeVisible();
+ });
+
test('Has FAQ section', async ({ page }) => {
const faqs = page.getByTestId(landingPageElements.faq);
await expect(faqs).toHaveCount(9);
diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts
index d00eafcc805..635f9fbd51a 100644
--- a/e2e/map.spec.ts
+++ b/e2e/map.spec.ts
@@ -6,42 +6,6 @@ test.beforeEach(async ({ page }) => {
});
const LANDING_PAGE_LINKS = [
- {
- slug: '2022/responsive-web-design',
- name: 'Responsive Web Design'
- },
- {
- slug: 'javascript-algorithms-and-data-structures-v8',
- name: 'JavaScript Algorithms and Data Structures'
- },
- {
- slug: 'front-end-development-libraries',
- name: 'Front End Development Libraries'
- },
- { slug: 'data-visualization', name: 'Data Visualization' },
- { slug: 'relational-database', name: 'Relational Database' },
- {
- slug: 'back-end-development-and-apis',
- name: 'Back End Development and APIs'
- },
- { slug: 'quality-assurance', name: 'Quality Assurance' },
- {
- slug: 'scientific-computing-with-python',
- name: 'Scientific Computing with Python'
- },
- {
- slug: 'data-analysis-with-python',
- name: 'Data Analysis with Python'
- },
- { slug: 'information-security', name: 'Information Security' },
- {
- slug: 'machine-learning-with-python',
- name: 'Machine Learning with Python'
- },
- {
- slug: 'college-algebra-with-python',
- name: 'College Algebra with Python'
- },
{
slug: 'full-stack-developer',
name: 'Certified Full Stack Developer Curriculum'
@@ -61,16 +25,7 @@ const LANDING_PAGE_LINKS = [
{ slug: 'the-odin-project', name: 'The Odin Project - freeCodeCamp Remix' },
{ slug: 'coding-interview-prep', name: 'Coding Interview Prep' },
{ slug: 'project-euler', name: 'Project Euler' },
- { slug: 'rosetta-code', name: 'Rosetta Code' },
- {
- slug: 'responsive-web-design',
- name: 'Legacy Responsive Web Design Challenges'
- },
- {
- slug: 'javascript-algorithms-and-data-structures',
- name: 'Legacy JavaScript Algorithms and Data Structures'
- },
- { slug: 'python-for-everybody', name: 'Legacy Python for Everybody' }
+ { slug: 'rosetta-code', name: 'Rosetta Code' }
];
test.describe('Map Component', () => {
@@ -85,7 +40,7 @@ test.describe('Map Component', () => {
page.getByText(translations.landing['interview-prep-heading'])
).toBeVisible();
const curriculumBtns = page.getByTestId('curriculum-map-button');
- await expect(curriculumBtns).toHaveCount(23);
+ await expect(curriculumBtns).toHaveCount(8);
for (const { name, slug } of LANDING_PAGE_LINKS) {
const superblockLink = page.getByRole('link', {
diff --git a/shared/config/curriculum.ts b/shared/config/curriculum.ts
index 91853e428f2..01ff5c45510 100644
--- a/shared/config/curriculum.ts
+++ b/shared/config/curriculum.ts
@@ -133,6 +133,8 @@ export const superBlockStages: StageMap = {
Object.freeze(superBlockStages);
+export const archivedSuperBlocks = superBlockStages[SuperBlockStage.Legacy];
+
export const catalogSuperBlocks = superBlockStages[SuperBlockStage.Catalog];
type NotAuditedSuperBlocks = {