Resolve merge conflicts

pull/1331/head
Kamran Ahmed 5 years ago
commit c1e01f70cf
  1. 84
      __tests__/path-map.spec.js
  2. 6
      components/featured-content/guides.js
  3. 1
      components/featured-content/index.js
  4. 6
      components/featured-content/roadmaps.js
  5. 8
      components/guide-block/index.js
  6. 2
      components/guide-body/index.js
  7. 1
      components/guide-body/style.js
  8. 54
      components/guide-footer/index.js
  9. 24
      components/guide-header/index.js
  10. 2
      components/roadmap-block/index.js
  11. 2
      components/roadmap-summary/index.js
  12. 26
      components/share-guide/index.js
  13. 12
      data/authors.json
  14. 36
      data/guides.json
  15. 0
      data/guides/bash-guide.md
  16. 0
      data/guides/dns-in-one-picture.md
  17. 0
      data/guides/http-caching.md
  18. 0
      data/guides/learn-regex.md
  19. 0
      data/guides/using-react-hooks.md
  20. 6
      data/roadmaps.json
  21. 6
      data/site.json
  22. 6
      layouts/guide/index.js
  23. 2
      lib/author.js
  24. 32
      lib/guide.js
  25. 15
      lib/roadmap.js
  26. 1
      lib/server.js
  27. 50
      lib/url.js
  28. 26
      next.config.js
  29. 10
      package.json
  30. 3
      pages/[fallback]/[version].js
  31. 22
      pages/[fallback]/index.js
  32. 0
      pages/[roadmap]/[version].js
  33. 12
      pages/[roadmap]/index.js
  34. 10
      pages/about.js
  35. 35
      pages/guides/[guide].js
  36. 4
      pages/guides/index.js
  37. 16
      pages/home.js
  38. 16
      pages/index.js
  39. 8
      pages/privacy.js
  40. 13
      pages/roadmaps.js
  41. 13
      pages/roadmaps/index.js
  42. 8
      pages/terms.js
  43. 87
      path-map.js
  44. 1673
      yarn.lock

@ -0,0 +1,84 @@
const path = require('path');
const fs = require('fs');
const guides = require('../data/guides');
const roadmaps = require('../data/roadmaps');
const {
getPageRoutes,
getGuideRoutes,
getRoadmapRoutes
} = require("../path-map");
describe("Build scripts tests", () => {
test('it should generate valid pathmap for pages', () => {
const pageRoutes = getPageRoutes();
expect(pageRoutes).toEqual({
'/': { page: '/index' },
'/about': { page: '/about' },
'/privacy': { page: '/privacy' },
'/terms': { page: '/terms' },
'/guides': { page: '/guides/index' },
'/roadmaps': { page: '/roadmaps' },
});
});
test('it should generate valid guides pathmap', () => {
const expectedGuideRoutes = guides.reduce((acc, guide) => {
const [,, slug] = guide.url.split('/');
return {
...acc,
[guide.url]: {
page: '/guides/[guide]',
query: slug,
}
};
}, {});
// Valid path map is generated
expect(expectedGuideRoutes).toEqual(getGuideRoutes());
const pageFilePath = path.join(__dirname, '../pages/guides/[guide].js');
const foundFilePath = fs.existsSync(pageFilePath) ? pageFilePath : '';
// Given page component exists
expect(foundFilePath).toEqual(pageFilePath);
});
test('it should have markdown file for each guide', () => {
guides.forEach(guide => {
const [,, slug] = guide.url.split('/');
const expectedFile = path.join(__dirname, `../data/guides/${slug}.md`);
const foundFile = fs.existsSync(expectedFile) ? expectedFile : '';
expect(foundFile).toEqual(expectedFile);
})
});
test('it should generate valid roadmap routes', () => {
const expectedPathMap = roadmaps.reduce((roadmapAcc, roadmap) => {
// Routes for each of the versions of this roadmap
const versionRoutes = roadmap.versions.reduce((versionAcc, version) => ({
...versionAcc,
[`${roadmap.url}/${version}`]: {
page: '/[roadmap]/[version]',
query: `${roadmap.url.split('/')[1]}/${version}`,
}
}), {});
// Route for the route roadmap itself
return {
...roadmapAcc,
[roadmap.url]: {
page: '/[roadmap]/index',
query: roadmap.url.split('/')[1]
},
// Expected roadmap for versions
...versionRoutes
};
}, {});
expect(getRoadmapRoutes()).toEqual(expectedPathMap);
})
});

@ -1,7 +1,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { FeaturedContentWrap } from './style'; import { FeaturedContentWrap } from './style';
import guides from '../../data/guides'; import guides from 'data/guides';
import GuideBlock from '../guide-block'; import GuideBlock from 'components/guide-block';
const FeaturedGuides = () => ( const FeaturedGuides = () => (
<FeaturedContentWrap className="featured-content-wrap"> <FeaturedContentWrap className="featured-content-wrap">
@ -16,7 +16,7 @@ const FeaturedGuides = () => (
{ guides { guides
.filter(({ featured }) => featured) .filter(({ featured }) => featured)
.map(guide => ( .map(guide => (
<GuideBlock guide={ guide } key={ guide.slug } /> <GuideBlock guide={ guide } key={ guide.url } />
)) } )) }
</div> </div>
</div> </div>

@ -1,5 +1,4 @@
import { FeaturedWrap } from './style'; import { FeaturedWrap } from './style';
import FeaturedJourneys from './journeys';
import FeaturedGuides from './guides'; import FeaturedGuides from './guides';
import FeaturedRoadmaps from './roadmaps'; import FeaturedRoadmaps from './roadmaps';

@ -1,7 +1,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { FeaturedContentWrap } from './style'; import { FeaturedContentWrap } from './style';
import roadmaps from '../../data/roadmaps'; import roadmaps from 'data/roadmaps';
import RoadmapBlock from '../roadmap-block'; import RoadmapBlock from 'components/roadmap-block';
const FeaturedRoadmaps = () => ( const FeaturedRoadmaps = () => (
<FeaturedContentWrap className="featured-content-wrap"> <FeaturedContentWrap className="featured-content-wrap">
@ -21,7 +21,7 @@ const FeaturedRoadmaps = () => (
{ roadmaps { roadmaps
.filter(({ featured }) => featured) .filter(({ featured }) => featured)
.map(roadmap => ( .map(roadmap => (
<RoadmapBlock roadmap={ roadmap } key={ roadmap.slug } /> <RoadmapBlock roadmap={ roadmap } key={ roadmap.url } />
)) } )) }
</div> </div>
</div> </div>

@ -1,12 +1,14 @@
import Link from 'next/link'; import Link from 'next/link';
import formatDate from 'date-fns/format'
import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style'; import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style';
import { findByUsername } from '../../lib/author'; import { findByUsername } from 'lib/author';
const GuideBlock = ({ guide }) => { const GuideBlock = ({ guide }) => {
const author = findByUsername(guide.author) || {}; const author = findByUsername(guide.author) || {};
return ( return (
<div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container"> <div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container">
<Link href={ guide.slug } passHref> <Link href={ guide.url } passHref>
<BlockLink> <BlockLink>
<BlockTitle>{ guide.title }</BlockTitle> <BlockTitle>{ guide.title }</BlockTitle>
<BlockSubtitle>{ guide.featuredDescription || guide.description }</BlockSubtitle> <BlockSubtitle>{ guide.featuredDescription || guide.description }</BlockSubtitle>
@ -15,7 +17,7 @@ const GuideBlock = ({ guide }) => {
<AuthorImage src={ author.picture } /> <AuthorImage src={ author.picture } />
<AuthorName>{ author.name }</AuthorName> <AuthorName>{ author.name }</AuthorName>
</Author> </Author>
<PublishDate>{ guide.updatedAt }</PublishDate> <PublishDate>{ formatDate(new Date(guide.createdAt), 'MMMM d, yyyy') }</PublishDate>
</BlockMeta> </BlockMeta>
</BlockLink> </BlockLink>
</Link> </Link>

@ -1,5 +1,5 @@
import { MDXProvider } from '@mdx-js/react'; import { MDXProvider } from '@mdx-js/react';
import MdxComponents from '../mdx-components'; import MdxComponents from 'components/mdx-components';
import { GuideBodyWrap } from './style'; import { GuideBodyWrap } from './style';
const GuideBody = (props) => ( const GuideBody = (props) => (

@ -5,4 +5,5 @@ export const GuideBodyWrap = styled.div`
max-width: 750px; max-width: 750px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;
min-height: 300px;
`; `;

@ -1,22 +1,46 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFacebookSquare, faGithub, faHackerNewsSquare, faRedditSquare, faTwitter, faTwitterSquare } from '@fortawesome/free-brands-svg-icons' import { faFacebookSquare, faGithub, faHackerNewsSquare, faRedditSquare, faTwitter, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
import { AuthorBio, AuthorImg, AuthorInfoWrap, AuthorMeta, ContributeIcon, FooterBg, FooterContainer, FooterWrap, ShareIcons, ShareWrap } from './style'; import { getContributionUrl } from "lib/guide";
import {
getTwitterUrl,
getTwitterShareUrl,
getFacebookShareUrl,
getRedditShareUrl,
getHnShareUrl
} from "lib/url";
import {
AuthorBio,
AuthorImg,
AuthorInfoWrap,
AuthorMeta,
ContributeIcon,
FooterBg,
FooterContainer,
FooterWrap,
ShareIcons,
ShareWrap
} from './style';
const GuideFooter = (props) => ( const GuideFooter = ({
guide,
guide: {
author = {}
} = {}
}) => (
<FooterWrap> <FooterWrap>
<FooterBg className="border-top"> <FooterBg className="border-top">
<FooterContainer> <FooterContainer>
<ShareWrap> <ShareWrap>
<ContributeIcon> <ContributeIcon>
<a href="#"> <a href={ getContributionUrl(guide) } target="_blank">
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Improve this Guide </span> <span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Improve this Guide </span>
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Contribute </span> <span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Contribute </span>
<FontAwesomeIcon icon={faGithub}/> <FontAwesomeIcon icon={faGithub}/>
</a> </a>
</ContributeIcon> </ContributeIcon>
<ContributeIcon hasMargins> <ContributeIcon hasMargins>
<a href="#"> <a href={ getTwitterUrl(author.twitter) } target="_blank">
<span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Follow the author </span> <span className="d-none d-sm-none d-md-inline d-lg-inline d-xl-inline">Follow the author </span>
<span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Author </span> <span className="d-inline d-sm-inline d-md-none d-lg-none d-xl-none">Author </span>
<FontAwesomeIcon icon={faTwitter}/> <FontAwesomeIcon icon={faTwitter}/>
@ -25,10 +49,18 @@ const GuideFooter = (props) => (
<ShareIcons> <ShareIcons>
<span className="d-none d-sm-none d-md-none d-lg-inline d-xl-inline">Help spread the word</span> <span className="d-none d-sm-none d-md-none d-lg-inline d-xl-inline">Help spread the word</span>
<span className="d-inline d-sm-inline d-md-inline d-lg-none d-xl-none">Share</span> <span className="d-inline d-sm-inline d-md-inline d-lg-none d-xl-none">Share</span>
<a href="#"><FontAwesomeIcon icon={faTwitterSquare}/></a> <a href={ getTwitterShareUrl({ text: `${guide.title} by @${author.twitter}`, url: guide.url })} target="_blank">
<a href="#"><FontAwesomeIcon icon={faFacebookSquare}/></a> <FontAwesomeIcon icon={faTwitterSquare}/>
<a href="#"><FontAwesomeIcon icon={faRedditSquare}/></a> </a>
<a href="#"><FontAwesomeIcon icon={faHackerNewsSquare}/></a> <a href={ getFacebookShareUrl({ text: guide.title, url: guide.url }) } target="_blank">
<FontAwesomeIcon icon={faFacebookSquare}/>
</a>
<a href={ getRedditShareUrl({ text: guide.title, url: guide.url })} target="_blank">
<FontAwesomeIcon icon={faRedditSquare}/>
</a>
<a href={ getHnShareUrl({ text: guide.title, url: guide.url })} target="_blank">
<FontAwesomeIcon icon={faHackerNewsSquare}/>
</a>
</ShareIcons> </ShareIcons>
</ShareWrap> </ShareWrap>
</FooterContainer> </FooterContainer>
@ -37,10 +69,10 @@ const GuideFooter = (props) => (
<FooterBg className="border-top"> <FooterBg className="border-top">
<FooterContainer> <FooterContainer>
<AuthorInfoWrap> <AuthorInfoWrap>
<AuthorImg src="/static/authors/kamranahmedse.jpeg" alt=""/> <AuthorImg src={ author.picture } alt={ author.name }/>
<AuthorMeta> <AuthorMeta>
<h4><a href="#">Kamran Ahmed</a></h4> <h4><a href={ getTwitterUrl(author.twitter) } target="_blank">{ author.name }</a></h4>
<AuthorBio>Lead engineer at Tajawal. Lover of all things web and opensource. Created roadmap.sh to help the confused ones.</AuthorBio> <AuthorBio>{ author.bio }</AuthorBio>
</AuthorMeta> </AuthorMeta>
</AuthorInfoWrap> </AuthorInfoWrap>
</FooterContainer> </FooterContainer>

@ -1,3 +1,4 @@
import formatDate from 'date-fns/format'
import { import {
ActionItems, ActionItems,
AuthorImage, AuthorImage,
@ -9,21 +10,28 @@ import {
GuideTitle, GuideTitle,
HeaderWrap, HeaderWrap,
} from './style'; } from './style';
import { getContributionUrl } from "lib/guide";
import { getTwitterUrl } from "lib/url";
const GuideHeader = (props) => ( const GuideHeader = ({
guide,
guide: {
author = {}
} = {}
}) => (
<HeaderWrap className="border-bottom"> <HeaderWrap className="border-bottom">
<GuideMeta> <GuideMeta>
<GuideAuthor href="https://github.com/kamranahmedse" target="_blank"> <GuideAuthor href={ getTwitterUrl(author.twitter) } target="_blank">
<AuthorImage src="/static/authors/kamranahmedse.jpeg" /> <AuthorImage src={ author.picture } />
Kamran Ahmed { author.name }
</GuideAuthor> </GuideAuthor>
&middot; &middot;
<GuideDate>Wednesday, October 9th 2019</GuideDate> <GuideDate>{ formatDate(new Date(guide.createdAt), 'EEEE, MMMM d yyyy') }</GuideDate>
&middot; &middot;
<EditGuide href="#">Improve this Guide</EditGuide> <EditGuide href={ getContributionUrl(guide) } target="_blank">Improve this Guide</EditGuide>
</GuideMeta> </GuideMeta>
<GuideTitle>Design Patterns for Humans</GuideTitle> <GuideTitle>{ guide.title }</GuideTitle>
<GuideSubtitle>An ultra-simplified explanation to design patterns</GuideSubtitle> <GuideSubtitle>{ guide.description }</GuideSubtitle>
<ActionItems> <ActionItems>
</ActionItems> </ActionItems>
</HeaderWrap> </HeaderWrap>

@ -3,7 +3,7 @@ import { BlockLink, BlockSubtitle, BlockTitle } from './style';
const RoadmapBlock = ({ roadmap }) => ( const RoadmapBlock = ({ roadmap }) => (
<div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container"> <div className="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container">
<Link href={ roadmap.slug } passHref> <Link href={ roadmap.url } passHref>
<BlockLink> <BlockLink>
<BlockTitle>{ roadmap.title }</BlockTitle> <BlockTitle>{ roadmap.title }</BlockTitle>
<BlockSubtitle>{ roadmap.featuredDescription || roadmap.description }</BlockSubtitle> <BlockSubtitle>{ roadmap.featuredDescription || roadmap.description }</BlockSubtitle>

@ -24,7 +24,7 @@ const RoadmapSummary = ({ roadmap }) => (
<Description>{ roadmap.description }</Description> <Description>{ roadmap.description }</Description>
<VersionList className="border-bottom"> <VersionList className="border-bottom">
{ (roadmap.versions || []).map(versionItem => ( { (roadmap.versions || []).map(versionItem => (
<Link href={ `${roadmap.slug}/${versionItem}` } passHref key={ versionItem }> <Link href={ `${roadmap.url}/${versionItem}` } passHref key={ versionItem }>
<VersionLink className={ classNames({ <VersionLink className={ classNames({
active: isActiveRoadmap(versionItem, roadmap.version), active: isActiveRoadmap(versionItem, roadmap.version),
}) }>{ versionItem } Version</VersionLink> }) }>{ versionItem } Version</VersionLink>

@ -1,14 +1,32 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFacebookSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons' import { faFacebookSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
import { getFacebookShareUrl, getRedditShareUrl, getTwitterShareUrl } from "lib/url";
import { ShareIcon, ShareIconsList, ShareWrap } from './style'; import { ShareIcon, ShareIconsList, ShareWrap } from './style';
const ShareGuide = (props) => ( const ShareGuide = ({
guide,
guide: {
author = {}
} = {}
}) => (
<ShareWrap> <ShareWrap>
<ShareIconsList className="d-sm-none d-md-none d-lg-flex d-xl-flex"> <ShareIconsList className="d-sm-none d-md-none d-lg-flex d-xl-flex">
<ShareIcon><a href="#"><FontAwesomeIcon icon={ faTwitterSquare } /></a></ShareIcon> <ShareIcon>
<ShareIcon><a href="#"><FontAwesomeIcon icon={ faFacebookSquare } /></a></ShareIcon> <a href={ getTwitterShareUrl({ text: `${guide.title} by @${author.twitter}`, url: guide.url })} target="_blank">
<ShareIcon><a href="#"><FontAwesomeIcon icon={ faRedditSquare } /></a></ShareIcon> <FontAwesomeIcon icon={ faTwitterSquare } />
</a>
</ShareIcon>
<ShareIcon>
<a href={ getFacebookShareUrl({ text: guide.title, url: guide.url }) } target="_blank">
<FontAwesomeIcon icon={ faFacebookSquare } />
</a>
</ShareIcon>
<ShareIcon>
<a href={ getRedditShareUrl({ text: guide.title, url: guide.url })} target="_blank">
<FontAwesomeIcon icon={ faRedditSquare } />
</a>
</ShareIcon>
</ShareIconsList> </ShareIconsList>
</ShareWrap> </ShareWrap>
); );

@ -2,16 +2,22 @@
{ {
"username": "kamranahmedse", "username": "kamranahmedse",
"name": "Kamran Ahmed", "name": "Kamran Ahmed",
"picture": "/static/authors/kamranahmedse.jpeg" "twitter": "kamranahmedse",
"picture": "/static/authors/kamranahmedse.jpeg",
"bio": "Lead engineer at Tajawal. Lover of all things web and opensource. Created roadmap.sh to help the confused ones."
}, },
{ {
"username": "ziishaned", "username": "ziishaned",
"name": "Zeeshan Ahmed", "name": "Zeeshan Ahmed",
"picture": "/static/authors/ziishaned.png" "twitter": "ziishaned",
"picture": "/static/authors/ziishaned.png",
"bio": "Playing with computers ever since I started walking. Loves hiking, cycling, JavaScript, OpenSource, challenging myself and communities."
}, },
{ {
"username": "idnan", "username": "idnan",
"name": "Adnan Ahmed", "name": "Adnan Ahmed",
"picture": "/static/authors/idnan.jpeg" "twitter": "idnan_se",
"picture": "/static/authors/idnan.jpeg",
"bio": "Playing with computers ever since I started walking. Loves hiking, cycling, JavaScript, OpenSource, challenging myself and communities."
} }
] ]

@ -2,55 +2,55 @@
{ {
"title": "Design Patterns for Humans", "title": "Design Patterns for Humans",
"description": "A language agnostic, ultra-simplified explanation to design patterns", "description": "A language agnostic, ultra-simplified explanation to design patterns",
"slug": "/guides/design-patterns-for-humans", "url": "/guides/design-patterns-for-humans",
"featured": true, "featured": true,
"author": "kamranahmedse", "author": "kamranahmedse",
"createdAt": "June 12, 2019", "createdAt": "2019-01-23T17:00:00.860Z",
"updatedAt": "June 12, 2019" "updatedAt": "2019-01-23T17:00:00.860Z"
}, },
{ {
"title": "Learn Regex", "title": "Learn Regex",
"description": "An easy to understand guide on regular expressions with real world examples", "description": "An easy to understand guide on regular expressions with real world examples",
"slug": "/guides/learn-regex", "url": "/guides/learn-regex",
"featured": true, "featured": true,
"author": "ziishaned", "author": "ziishaned",
"createdDate": "June 19, 2019", "createdAt": "2019-01-23T17:00:00.860Z",
"updatedAt": "June 19, 2019" "updatedAt": "2019-01-23T17:00:00.860Z"
}, },
{ {
"title": "Bash Guide", "title": "Bash Guide",
"description": "Easy to understand guide for bash with real world usage examples.", "description": "Easy to understand guide for bash with real world usage examples.",
"slug": "/guides/bash-guide", "url": "/guides/bash-guide",
"featured": true, "featured": true,
"author": "idnan", "author": "idnan",
"createdAt": "May 18, 2018", "createdAt": "2019-01-23T17:00:00.860Z",
"updatedAt": "May 18, 2018" "updatedAt": "2019-01-23T17:00:00.860Z"
}, },
{ {
"title": "DNS in One Picture", "title": "DNS in One Picture",
"description": "Quick illustrative guide on how a website is found on the internet.", "description": "Quick illustrative guide on how a website is found on the internet.",
"slug": "/guides/dns-in-one-picture", "url": "/guides/dns-in-one-picture",
"featured": true, "featured": true,
"author": "kamranahmedse", "author": "kamranahmedse",
"createdAt": "May 11, 2018", "createdAt": "2019-11-01T12:00:00.860Z",
"updatedAt": "May 11, 2018" "updatedAt": "2019-11-01T12:00:00.860Z"
}, },
{ {
"title": "Using React Hooks", "title": "Using React Hooks",
"description": "Start using React hooks in your react applications today with this guide.", "description": "Start using React hooks in your react applications today with this guide.",
"slug": "/guides/using-react-hooks", "url": "/guides/using-react-hooks",
"featured": true, "featured": true,
"author": "kamranahmedse", "author": "kamranahmedse",
"createdAt": "October 22, 2019", "createdAt": "2019-01-23T17:00:00.860Z",
"updatedAt": "October 22, 2019" "updatedAt": "2019-01-23T17:00:00.860Z"
}, },
{ {
"title": "HTTP Caching", "title": "HTTP Caching",
"description": "Everything you need to know about web caching", "description": "Everything you need to know about web caching",
"slug": "/guides/http-caching", "url": "/guides/http-caching",
"featured": true, "featured": true,
"author": "kamranahmedse", "author": "kamranahmedse",
"updatedAt": "November 01, 2019", "updatedAt": "2019-10-09T12:00:00.860Z",
"createdAt": "November 01, 2019" "createdAt": "2019-01-23T17:00:00.860Z"
} }
] ]

@ -3,7 +3,7 @@
"title": "Frontend Developer", "title": "Frontend Developer",
"description": "Step by step guide to becoming a modern 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", "featuredDescription": "Step by step guide to becoming a modern frontend developer in 2019",
"slug": "/roadmaps/frontend", "url": "/frontend",
"picture": "/static/roadmaps/{version}/frontend.png", "picture": "/static/roadmaps/{version}/frontend.png",
"featured": true, "featured": true,
"versions": [ "versions": [
@ -16,7 +16,7 @@
"title": "Backend Developer", "title": "Backend Developer",
"description": "Step by step guide to becoming a modern 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", "featuredDescription": "Step by step guide to becoming a modern backend developer in 2019",
"slug": "/roadmaps/backend", "url": "/backend",
"picture": "/static/roadmaps/{version}/backend.png", "picture": "/static/roadmaps/{version}/backend.png",
"featured": true, "featured": true,
"versions": [ "versions": [
@ -29,7 +29,7 @@
"title": "DevOps Roadmap", "title": "DevOps Roadmap",
"description": "Step by step guide 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", "featuredDescription": "Step by step guide to become an SRE or for any operations role in 2019",
"slug": "/roadmaps/devops", "url": "/devops",
"picture": "/static/roadmaps/{version}/devops.png", "picture": "/static/roadmaps/{version}/devops.png",
"featured": true, "featured": true,
"versions": [ "versions": [

@ -0,0 +1,6 @@
{
"twitter": "roadmapsh",
"url": "https://roadmap.sh",
"repoUrl": "https://github.com/kamranahmedse/roadmap-next",
"dataUrl": "/tree/master/data"
}

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import DefaultLayout from '../default'; import DefaultLayout from 'layouts/default';
import PageHeader from '../../components/page-header'; import PageHeader from 'components/page-header';
import PageFooter from '../../components/page-footer'; import PageFooter from 'components/page-footer';
class GuideLayout extends React.Component { class GuideLayout extends React.Component {
render() { render() {

@ -1,3 +1,3 @@
import authors from "../data/authors"; import authors from "data/authors";
export const findByUsername = (username) => authors.find(author => author.username === username) || {}; export const findByUsername = (username) => authors.find(author => author.username === username) || {};

@ -0,0 +1,32 @@
import guides from "data/guides";
import authors from "data/authors";
import siteConfig from "data/site";
export const getRequestedGuide = req => {
const guide = guides.find(guide => guide.url === req.url);
if (!guide) {
return null;
}
// We will use this URL format to find the relevant markdown
// file inside the `/data` directory. For example `/guides/learn-regex`
// has to have `/guides/learn-regex.md` file inside the `data` directory
const path = guide.url.replace(/^\//, '');
try {
return {
...guide,
author: authors.find(author => author.username === guide.author) || {},
component: require(`../data/${path}.md`).default,
};
} catch (e) {
console.log(e);
}
return null;
};
export const getContributionUrl = (guide) => {
return `${siteConfig.repoUrl}${siteConfig.dataUrl}${guide.url}.md`
};

@ -1,25 +1,20 @@
import roadmaps from "../data/roadmaps"; import roadmaps from "data/roadmaps";
export const getRequestedRoadmap = req => { export const getRequestedRoadmap = req => {
// Considering it a new roadmap URL e.g. `/roadmaps/frontend` // Considering it a new roadmap URL e.g. `/roadmaps/frontend`
const currentUrl = req.url.replace(/\/$/, ''); 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` // Get the roadmap version out of the URL e.g. `/roadmaps/frontend/2019`
const [foundVersion = ''] = currentUrl.match(/(\d+|latest)$/) || ['latest']; const [foundVersion = ''] = currentUrl.match(/(\d+|latest)$/) || ['latest'];
const foundVersionRegex = new RegExp(`\/?${foundVersion}$`); const foundVersionRegex = new RegExp(`\/?${foundVersion}$`);
// Remove version from the URL because slugs in roadmaps list don't have versions // Remove version from the URL because urls in roadmaps list don't have versions
const newUrlWithoutVersion = currentUrl.replace(foundVersionRegex, ''); const urlWithoutVersion = currentUrl.replace(foundVersionRegex, '');
const legacyUrlWithoutVersion = legacyUrl.replace(foundVersionRegex, '');
const urlToSlugList = [ const urlToSlugList = [
currentUrl, currentUrl,
legacyUrl, urlWithoutVersion,
newUrlWithoutVersion,
legacyUrlWithoutVersion,
]; ];
const foundRoadmap = roadmaps.find(roadmap => urlToSlugList.includes(roadmap.slug)); const foundRoadmap = roadmaps.find(roadmap => urlToSlugList.includes(roadmap.url));
if (!foundRoadmap) { if (!foundRoadmap) {
return null; return null;
} }

@ -6,6 +6,7 @@
export const serverOnlyProps = (callback) => { export const serverOnlyProps = (callback) => {
return async (props) => { return async (props) => {
if (process.browser) { if (process.browser) {
// noinspection ES6ModulesDependencies,JSUnresolvedVariable
return __NEXT_DATA__.props.pageProps; return __NEXT_DATA__.props.pageProps;
} }

@ -0,0 +1,50 @@
import queryString from 'query-string';
import siteConfig from 'data/site';
export const prefixHost = (url) => {
return /^\//.test(url) ? `${siteConfig.url}${url}` : url;
};
export const getTwitterUrl = (username) => {
return `https://twitter.com/${username}`;
};
export const getTwitterShareUrl = ({ text, url }) => {
const urlToShare = `${prefixHost(url)}?${queryString.stringify({
utm_source: 'roadmap.sh',
utm_campaign: 'share',
utm_medium: 'twitter',
})}`;
return `https://twitter.com/intent/tweet?text=${text}&url=${encodeURI(urlToShare)}`;
};
export const getFacebookShareUrl = ({ text, url }) => {
const urlToShare = `${prefixHost(url)}?${queryString.stringify({
utm_source: 'roadmap.sh',
utm_campaign: 'share',
utm_medium: 'facebook',
})}`;
return `https://www.facebook.com/sharer/sharer.php?quote=${text}&u=${encodeURI(urlToShare)}`;
};
export const getRedditShareUrl = ({ text, url }) => {
const urlToShare = `${prefixHost(url)}?${queryString.stringify({
utm_source: 'roadmap.sh',
utm_campaign: 'share',
utm_medium: 'reddit'
})}`;
return `https://www.reddit.com/submit?title=${text}&url=${encodeURI(urlToShare)}`;
};
export const getHnShareUrl = ({ text, url }) => {
const urlToShare = `${prefixHost(url)}?${queryString.stringify({
utm_source: 'roadmap.sh',
utm_campaign: 'share',
utm_medium: 'hn'
})}`;
return `https://news.ycombinator.com/submitlink?t=${text}&u=${urlToShare}`;
};

@ -1,6 +1,12 @@
const path = require('path');
const withSass = require('@zeit/next-sass'); const withSass = require('@zeit/next-sass');
const withCSS = require('@zeit/next-css'); const withCSS = require('@zeit/next-css');
const rehypePrism = require('@mapbox/rehype-prism') const rehypePrism = require('@mapbox/rehype-prism');
const {
getPageRoutes,
getGuideRoutes,
getRoadmapRoutes,
} = require("./path-map");
const withMDX = require('@next/mdx')({ const withMDX = require('@next/mdx')({
extension: /\.(md|mdx)?$/, extension: /\.(md|mdx)?$/,
@ -11,20 +17,10 @@ const withMDX = require('@next/mdx')({
const options = { const options = {
exportPathMap: () => { exportPathMap: () => {
// @todo make it dynamic for pages, authors and guides
return { return {
'/': { page: '/' }, ...getPageRoutes(),
'/about': { page: '/about' }, ...getGuideRoutes(),
'/privacy': { page: '/privacy' }, ...getRoadmapRoutes(),
'/terms': { page: '/terms' },
'/roadmaps': { page: '/roadmaps' },
'/guides': { page: '/guides' },
'/frontend': { page: '/[fallback]', query: "frontend" },
'/backend': { page: '/[fallback]', query: "backend" },
'/devops': { page: '/[fallback]', query: "devops" },
'/roadmaps/frontend': { page: '/roadmaps/[roadmap]', query: "frontend" },
'/roadmaps/backend': { page: '/roadmaps/[roadmap]', query: "backend" },
'/roadmaps/devops': { page: '/roadmaps/[roadmap]', query: "devops" },
}; };
}, },
@ -38,6 +34,8 @@ const options = {
use: ['@svgr/webpack'], use: ['@svgr/webpack'],
}); });
config.resolve.modules.push(path.resolve('./'));
// Allow loading images // Allow loading images
config.module.rules.push({ config.module.rules.push({
test: /\.(png|jpg|gif|eot|ttf|woff|woff2)$/, test: /\.(png|jpg|gif|eot|ttf|woff|woff2)$/,

@ -7,7 +7,9 @@
"dev": "next", "dev": "next",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"html": "next export" "html": "next export",
"test": "jest",
"test:watch": "jest --watch"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.22", "@fortawesome/fontawesome-svg-core": "^1.2.22",
@ -24,16 +26,20 @@
"autoprefixer": "^9.6.1", "autoprefixer": "^9.6.1",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"date-fns": "^2.6.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"next": "^9.0.4", "next": "^9.0.4",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"postcss-css-variables": "^0.13.0", "postcss-css-variables": "^0.13.0",
"prism-themes": "^1.3.0", "prism-themes": "^1.3.0",
"query-string": "^6.8.3",
"react": "^16.9.0", "react": "^16.9.0",
"react-dom": "^16.9.0", "react-dom": "^16.9.0",
"styled-components": "^4.4.0" "styled-components": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
"babel-plugin-styled-components": "^1.10.6" "babel-plugin-styled-components": "^1.10.6",
"glob": "^7.1.5",
"jest": "^24.9.0"
} }
} }

@ -1,3 +0,0 @@
import OldRoadmap from './index';
export default OldRoadmap;

@ -1,22 +0,0 @@
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 }) => {
if (roadmap) {
return <Roadmap roadmap={ roadmap } />
}
return <Error statusCode={ 404 } />;
};
OldRoadmap.getInitialProps = serverOnlyProps(({ req }) => {
return {
roadmap: getRequestedRoadmap(req),
};
});
export default OldRoadmap;

@ -1,10 +1,10 @@
import Error from 'next/error'; import Error from 'next/error';
import DefaultLayout from '../../../layouts/default'; import DefaultLayout from 'layouts/default';
import { serverOnlyProps } from '../../../lib/server'; import PageHeader from 'components/page-header';
import PageHeader from '../../../components/page-header'; import PageFooter from 'components/page-footer';
import PageFooter from '../../../components/page-footer'; import RoadmapSummary from 'components/roadmap-summary';
import { getRequestedRoadmap } from '../../../lib/roadmap'; import { serverOnlyProps } from 'lib/server';
import RoadmapSummary from '../../../components/roadmap-summary'; import { getRequestedRoadmap } from 'lib/roadmap';
const Roadmap = ({ roadmap }) => { const Roadmap = ({ roadmap }) => {
if (!roadmap) { if (!roadmap) {

@ -1,8 +1,8 @@
import AboutHeader from '../components/about-header/index'; import AboutHeader from 'components/about-header/index';
import PageFooter from '../components/page-footer/index'; import PageFooter from 'components/page-footer/index';
import PageHeader from '../components/page-header/index'; import PageHeader from 'components/page-header/index';
import DefaultLayout from '../layouts/default/index'; import DefaultLayout from 'layouts/default/index';
import FaqList from '../components/faq-list/index'; import FaqList from 'components/faq-list/index';
const About = () => ( const About = () => (
<DefaultLayout> <DefaultLayout>

@ -1,33 +1,32 @@
import GuideLayout from '../../layouts/guide'; import Error from "next/error";
import { serverOnlyProps } from '../../lib/server'; import GuideLayout from 'layouts/guide';
import { serverOnlyProps } from 'lib/server';
import GuideHeader from '../../components/guide-header'; import GuideHeader from 'components/guide-header';
import GuideContent from '../../data/guides/keep-it-clean.md'; import GuideBody from 'components/guide-body';
import GuideBody from '../../components/guide-body'; import ShareGuide from 'components/share-guide';
import ShareGuide from '../../components/share-guide'; import GuideFooter from 'components/guide-footer';
import GuideFooter from '../../components/guide-footer'; import { getRequestedGuide } from "lib/guide";
const Guide = ({ guide }) => { const Guide = ({ guide }) => {
if (!guide) {
return <Error statusCode={404} />
}
return ( return (
<GuideLayout> <GuideLayout>
<GuideHeader/> <GuideHeader guide={ guide } />
<GuideBody> <GuideBody>
<GuideContent/> <guide.component />
<ShareGuide/> <ShareGuide guide={ guide }/>
</GuideBody> </GuideBody>
<GuideFooter/> <GuideFooter guide={ guide } />
</GuideLayout> </GuideLayout>
); );
}; };
Guide.getInitialProps = serverOnlyProps(({ req }) => { Guide.getInitialProps = serverOnlyProps(({ req }) => {
// Remove URL chunk to make it a slug e.g. /guides/some-guide-item to become `some-guide-item
const slug = req.url
.replace(/^\/*?guides\/*?/, '/')
.replace(/\/*$/, '');
return { return {
slug, guide: getRequestedGuide(req)
}; };
}); });

@ -1,5 +1,5 @@
import DefaultLayout from '../../layouts/default/index'; import DefaultLayout from 'layouts/default/index';
import PageHeader from '../../components/page-header/index'; import PageHeader from 'components/page-header/index';
const Roadmap = () => ( const Roadmap = () => (
<DefaultLayout> <DefaultLayout>

@ -1,16 +0,0 @@
import FeaturedContent from '../components/featured-content/index';
import HeroSection from '../components/hero-section/index';
import PageFooter from '../components/page-footer/index';
import PageHeader from '../components/page-header/index';
import DefaultLayout from '../layouts/default/index';
const Home = (props) => (
<DefaultLayout>
<PageHeader />
<HeroSection />
<FeaturedContent />
<PageFooter />
</DefaultLayout>
);
export default Home;

@ -1,10 +1,16 @@
import Home from './home'; import FeaturedContent from 'components/featured-content/index';
import DefaultLayout from '../layouts/default'; import HeroSection from 'components/hero-section/index';
import PageFooter from 'components/page-footer/index';
import PageHeader from 'components/page-header/index';
import DefaultLayout from 'layouts/default/index';
const Index = () => ( const Home = (props) => (
<DefaultLayout> <DefaultLayout>
<Home /> <PageHeader />
<HeroSection />
<FeaturedContent />
<PageFooter />
</DefaultLayout> </DefaultLayout>
); );
export default Index; export default Home;

@ -1,7 +1,7 @@
import PageHeader from '../components/page-header/index'; import PageHeader from 'components/page-header/index';
import PageFooter from '../components/page-footer/index'; import PageFooter from 'components/page-footer/index';
import { TosPage } from '../components/tos-page/index'; import { TosPage } from 'components/tos-page/index';
import DefaultLayout from '../layouts/default/index'; import DefaultLayout from 'layouts/default/index';
const Privacy = () => ( const Privacy = () => (
<DefaultLayout> <DefaultLayout>

@ -0,0 +1,13 @@
import DefaultLayout from 'layouts/default/index';
import PageHeader from 'components/page-header/index';
const RoadmapsList = () => (
<DefaultLayout>
<PageHeader />
<div className="container">
<p>Show all roadmaps here</p>
</div>
</DefaultLayout>
);
export default RoadmapsList;

@ -1,13 +0,0 @@
import DefaultLayout from '../../layouts/default/index';
import PageHeader from '../../components/page-header/index';
const Roadmap = () => (
<DefaultLayout>
<PageHeader />
<div className="container">
<p>Show all roadmaps here</p>
</div>
</DefaultLayout>
);
export default Roadmap;

@ -1,7 +1,7 @@
import PageFooter from '../components/page-footer/index'; import PageFooter from 'components/page-footer/index';
import PageHeader from '../components/page-header/index'; import PageHeader from 'components/page-header/index';
import { TosPage } from '../components/tos-page/index'; import { TosPage } from 'components/tos-page/index';
import DefaultLayout from '../layouts/default/index'; import DefaultLayout from 'layouts/default/index';
const Terms = () => ( const Terms = () => (
<DefaultLayout> <DefaultLayout>

@ -0,0 +1,87 @@
const path = require('path');
const glob = require('glob');
const guides = require('./data/guides.json');
const roadmaps = require('./data/roadmaps');
const PAGES_PATH = path.join(__dirname, 'pages');
/**
* Generate the page routes from the page files inside `/pages`
* directory. Gives the format understood by next
* {
* '/slug': { page: '/path/to-file' }
* }
*/
const getPageRoutes = () => {
const files = glob.sync(`${PAGES_PATH}/**/*.js`, {
ignore: [
'**/_*.js', // private non-page files e.g. _document.js
'**/[[]*[]].js', // Ignore dynamic pages i.e. `page/[something].js` files
'**/[[]*[]]/*.js', // Ignore files inside dynamic pages i.e. `[something]/abc.js`
]
});
const pageRoutes = {};
files.forEach(file => {
const pageName = file.replace(PAGES_PATH, '').replace('.js', '');
const pagePath = pageName.replace('/index', '') || '/';
pageRoutes[pagePath] = { page: `${pageName}` }
});
return pageRoutes;
};
/**
* Generates routes for guide pages
* @returns {*}
*/
const getGuideRoutes = () => {
return guides.reduce((acc, guide) => {
const [, , slug] = guide.url.split('/');
return {
...acc,
[guide.url]: {
page: '/guides/[guide]',
query: slug,
}
};
}, {});
};
/**
* Generates routes for each of the roadmap and it's respective versions
* @returns {*}
*/
const getRoadmapRoutes = () => {
return roadmaps.reduce((roadmapRoutes, roadmap) => {
const [, slug] = roadmap.url.split('/');
return {
...roadmapRoutes,
// Default roadmap path i.e. `{ '/frontend': { page: '/[roadmap]/index', query: 'frontend' }`
[roadmap.url]: {
page: '/[roadmap]/index',
query: slug
},
// Route for each of the versions of this roadmap i.e.
// `{ '/frontend/2019': { page: '/[roadmap]/[version]', query: 'frontend/2019' } }`
...(roadmap.versions.reduce((versionRoutes, version) => {
return {
...versionRoutes,
[`${roadmap.url}/${version}`]: {
page: '/[roadmap]/[version]',
query: `${slug}/${version}`
}
};
}, {})),
};
}, {});
};
module.exports = {
getPageRoutes,
getGuideRoutes,
getRoadmapRoutes,
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save