diff --git a/components/featured-content/roadmaps.js b/components/featured-content/roadmaps.js
index aa0230c5f..a47991fc8 100644
--- a/components/featured-content/roadmaps.js
+++ b/components/featured-content/roadmaps.js
@@ -20,7 +20,7 @@ const FeaturedRoadmaps = () => (
{ roadmaps
.filter(({ featured }) => featured)
.map(roadmap => (
-
+
{ roadmap.title }
diff --git a/components/roadmap-summary/index.js b/components/roadmap-summary/index.js
index ffca086f8..775281c7c 100644
--- a/components/roadmap-summary/index.js
+++ b/components/roadmap-summary/index.js
@@ -1,3 +1,6 @@
+import Link from 'next/link';
+import classNames from 'classnames';
+
import {
SummaryContainer,
Title,
@@ -5,6 +8,8 @@ import {
Image,
Header,
Summary,
+ VersionLink,
+ VersionList,
} from './style';
const RoadmapSummary = ({ roadmap }) => (
@@ -12,8 +17,17 @@ const RoadmapSummary = ({ roadmap }) => (
{ roadmap.title }
{ roadmap.description }
+
+ { (roadmap.versions || []).map(versionItem => (
+
+ { versionItem } Version
+
+ )) }
+
-
+
diff --git a/components/roadmap-summary/style.js b/components/roadmap-summary/style.js
index e7e73d2a2..94ece8d5d 100644
--- a/components/roadmap-summary/style.js
+++ b/components/roadmap-summary/style.js
@@ -9,7 +9,7 @@ export const Header = styled.div`
export const Summary = styled.div`
text-align: center;
- padding: 50px 0;
+ padding: 0 0 50px;
`;
export const Title = styled.h1`
@@ -17,10 +17,46 @@ export const Title = styled.h1`
margin-bottom: 12px;
font-size: 42px;
`;
+
export const Description = styled.p`
font-size: 19px;
- margin-bottom: 0;
`;
+
export const Image = styled.img`
width: 100%;
`;
+
+export const VersionList = styled.div`
+ margin: 35px 0 15px;
+`;
+
+export const VersionLink = styled.a`
+ display: inline-block;
+ position: relative;
+ padding: 5px 10px 8px;
+ text-decoration: none;
+ color: rgb(102, 102, 102);
+ font-size: 14px;
+ font-weight: 400;
+ text-transform: capitalize;
+
+ &.active, &.active:hover {
+ color: #2d2d2d;
+
+ &:after {
+ position: absolute;
+ content: "";
+ display: block;
+ height: 0;
+ left: 9px;
+ right: 9px;
+ bottom: -1px;
+ border-bottom: 2px solid currentColor;
+ }
+ }
+
+ &:hover {
+ text-decoration: none;
+ color: #111111;
+ }
+`;
\ No newline at end of file
diff --git a/data/roadmaps.json b/data/roadmaps.json
index 05b85464a..763c130bf 100644
--- a/data/roadmaps.json
+++ b/data/roadmaps.json
@@ -3,24 +3,39 @@
"title": "Frontend Developer",
"description": "Step by step guide to becoming a modern frontend developer",
"featuredDescription": "Step by step guide to becoming a modern frontend developer in 2019",
- "slug": "/frontend",
+ "slug": "/roadmaps/frontend",
"picture": "/static/roadmaps/frontend.png",
- "featured": true
+ "featured": true,
+ "versions": [
+ "latest",
+ "2018",
+ "2017"
+ ]
},
{
"title": "Backend Developer",
- "description": "Roadmap to becoming a backend developer",
+ "description": "Step by step guide to becoming a modern backend developer",
"featuredDescription": "Step by step guide to becoming a modern backend developer in 2019",
- "slug": "/backend",
+ "slug": "/roadmaps/backend",
"picture": "/static/roadmaps/backend.png",
- "featured": true
+ "featured": true,
+ "versions": [
+ "latest",
+ "2018",
+ "2017"
+ ]
},
{
"title": "DevOps Roadmap",
- "description": "Roadmap for DevOps or any other Operations Role",
+ "description": "Step by step guide for DevOps or any other Operations Role",
"featuredDescription": "Step by step guide to become an SRE or for any operations role in 2019",
- "slug": "/devops",
+ "slug": "/roadmaps/devops",
"picture": "/static/roadmaps/devops.png",
- "featured": true
+ "featured": true,
+ "versions": [
+ "latest",
+ "2018",
+ "2017"
+ ]
}
]
\ No newline at end of file
diff --git a/layouts/default/global.scss b/layouts/default/global.scss
index 86c68b481..a1e978fc6 100644
--- a/layouts/default/global.scss
+++ b/layouts/default/global.scss
@@ -16,6 +16,10 @@
background-color: #fafafa !important;
}
+.muted {
+ color: #757575;
+}
+
.dark-link {
font-weight: 500;
color: #000000;
diff --git a/lib/roadmap.js b/lib/roadmap.js
new file mode 100644
index 000000000..c9798295e
--- /dev/null
+++ b/lib/roadmap.js
@@ -0,0 +1,26 @@
+import roadmaps from "../data/roadmaps";
+
+export const getRequestedRoadmap = req => {
+ // Considering it a new roadmap URL e.g. `/roadmaps/frontend`
+ const currentUrl = req.url.replace(/\/$/, '');
+ // Considering it a legacy URL e.g. converting `/frontend` to `roadmap.sh/roadmaps/frontend`
+ const legacyUrl = `/roadmaps${currentUrl}`;
+ // Get the roadmap version out of the URL e.g. `/roadmaps/frontend/2019`
+ const [foundVersion = ''] = currentUrl.match(/(\d+|latest)$/) || ['latest'];
+ const foundVersionRegex = new RegExp(`\/?${foundVersion}$`);
+ // Remove version from the URL because slugs in roadmaps list don't have versions
+ const newUrlWithoutVersion = currentUrl.replace(foundVersionRegex, '');
+ const legacyUrlWithoutVersion = legacyUrl.replace(foundVersionRegex, '');
+
+ const urlToSlugList = [
+ currentUrl,
+ legacyUrl,
+ newUrlWithoutVersion,
+ legacyUrlWithoutVersion,
+ ];
+
+ return {
+ ...roadmaps.find(roadmap => urlToSlugList.includes(roadmap.slug)),
+ version: foundVersion,
+ };
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index 0dee1a905..8731f777c 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@zeit/next-sass": "^1.0.1",
"autoprefixer": "^9.6.1",
"bootstrap": "^4.3.1",
+ "classnames": "^2.2.6",
"font-awesome": "^4.7.0",
"next": "^9.0.4",
"node-sass": "^4.12.0",
diff --git a/pages/[fallback]/[version].js b/pages/[fallback]/[version].js
new file mode 100644
index 000000000..9d2cd34f1
--- /dev/null
+++ b/pages/[fallback]/[version].js
@@ -0,0 +1,3 @@
+import OldRoadmap from './index';
+
+export default OldRoadmap;
\ No newline at end of file
diff --git a/pages/[fallback].js b/pages/[fallback]/index.js
similarity index 51%
rename from pages/[fallback].js
rename to pages/[fallback]/index.js
index 204b54e88..3dc355bed 100644
--- a/pages/[fallback].js
+++ b/pages/[fallback]/index.js
@@ -1,6 +1,7 @@
-import Roadmap from './roadmaps/[roadmap]';
-import roadmapsList from "../data/roadmaps.json";
-import { serverOnlyProps } from '../lib/server';
+import Error from 'next/error';
+import Roadmap from '../roadmaps/[roadmap]/index';
+import { serverOnlyProps } from '../../lib/server';
+import { getRequestedRoadmap } from '../../lib/roadmap';
// Fallback page to handle the old roadmap pages implementation
const OldRoadmap = ({ roadmap }) => {
@@ -8,12 +9,12 @@ const OldRoadmap = ({ roadmap }) => {
return
}
- return 404
+ return ;
};
OldRoadmap.getInitialProps = serverOnlyProps(({ req }) => {
return {
- roadmap: roadmapsList.find(roadmap => roadmap.slug === req.url),
+ roadmap: getRequestedRoadmap(req),
};
});
diff --git a/pages/roadmaps/[roadmap].js b/pages/roadmaps/[roadmap].js
deleted file mode 100644
index 31a75e18c..000000000
--- a/pages/roadmaps/[roadmap].js
+++ /dev/null
@@ -1,25 +0,0 @@
-import roadmaps from "../../data/roadmaps";
-import DefaultLayout from '../../layouts/default/index';
-import PageHeader from '../../components/page-header/index';
-import { serverOnlyProps } from '../../lib/server';
-import RoadmapSummary from '../../components/roadmap-summary';
-import PageFooter from '../../components/page-footer';
-
-const Roadmap = ({ roadmap }) => {
- return (
-
-
-
-
-
- );
-};
-
-Roadmap.getInitialProps = serverOnlyProps(({ req }) => {
- const normalizedUrl = req.url.replace('roadmaps/', '');
- return {
- roadmap: roadmaps.find(roadmap => roadmap.slug === normalizedUrl),
- };
-});
-
-export default Roadmap;
\ No newline at end of file
diff --git a/pages/roadmaps/[roadmap]/[version].js b/pages/roadmaps/[roadmap]/[version].js
new file mode 100644
index 000000000..8ab4b316d
--- /dev/null
+++ b/pages/roadmaps/[roadmap]/[version].js
@@ -0,0 +1,3 @@
+import Roadmap from './index';
+
+export default Roadmap;
\ No newline at end of file
diff --git a/pages/roadmaps/[roadmap]/index.js b/pages/roadmaps/[roadmap]/index.js
new file mode 100644
index 000000000..d020a9e3f
--- /dev/null
+++ b/pages/roadmaps/[roadmap]/index.js
@@ -0,0 +1,24 @@
+import DefaultLayout from '../../../layouts/default';
+import { serverOnlyProps } from '../../../lib/server';
+import PageHeader from '../../../components/page-header';
+import PageFooter from '../../../components/page-footer';
+import { getRequestedRoadmap } from '../../../lib/roadmap';
+import RoadmapSummary from '../../../components/roadmap-summary';
+
+const Roadmap = ({ roadmap }) => {
+ return (
+
+
+
+
+
+ );
+};
+
+Roadmap.getInitialProps = serverOnlyProps(({ req }) => {
+ return {
+ roadmap: getRequestedRoadmap(req),
+ };
+});
+
+export default Roadmap;
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index a5f5761a1..688a1fc88 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1754,6 +1754,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
+classnames@^2.2.6:
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
+ integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
+
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"