Resolve merge conflicts
This commit is contained in:
84
__tests__/path-map.spec.js
Normal file
84
__tests__/path-map.spec.js
Normal file
@ -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,11 +16,11 @@ 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>
|
||||||
</FeaturedContentWrap>
|
</FeaturedContentWrap>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default FeaturedGuides;
|
export default FeaturedGuides;
|
||||||
|
@ -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';
|
||||||
|
|
||||||
@ -14,4 +13,4 @@ FeaturedContent.defaultProps = {
|
|||||||
className: '',
|
className: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FeaturedContent;
|
export default FeaturedContent;
|
||||||
|
@ -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,11 +21,11 @@ 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>
|
||||||
</FeaturedContentWrap>
|
</FeaturedContentWrap>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default FeaturedRoadmaps;
|
export default FeaturedRoadmaps;
|
||||||
|
@ -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>
|
||||||
@ -23,4 +25,4 @@ const GuideBlock = ({ guide }) => {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GuideBlock;
|
export default GuideBlock;
|
||||||
|
@ -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) => (
|
||||||
@ -10,4 +10,4 @@ const GuideBody = (props) => (
|
|||||||
</MDXProvider>
|
</MDXProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default GuideBody;
|
export default GuideBody;
|
||||||
|
@ -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>
|
||||||
·
|
·
|
||||||
<GuideDate>Wednesday, October 9th 2019</GuideDate>
|
<GuideDate>{ formatDate(new Date(guide.createdAt), 'EEEE, MMMM d yyyy') }</GuideDate>
|
||||||
·
|
·
|
||||||
<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>
|
||||||
@ -12,4 +12,4 @@ const RoadmapBlock = ({ roadmap }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default RoadmapBlock;
|
export default RoadmapBlock;
|
||||||
|
@ -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>
|
||||||
@ -40,4 +40,4 @@ const RoadmapSummary = ({ roadmap }) => (
|
|||||||
</SummaryContainer>
|
</SummaryContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default RoadmapSummary;
|
export default RoadmapSummary;
|
||||||
|
@ -1,16 +1,34 @@
|
|||||||
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>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ShareGuide;
|
export default ShareGuide;
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
0
data/guides/bash-guide.md
Normal file
0
data/guides/bash-guide.md
Normal file
0
data/guides/dns-in-one-picture.md
Normal file
0
data/guides/dns-in-one-picture.md
Normal file
0
data/guides/http-caching.md
Normal file
0
data/guides/http-caching.md
Normal file
0
data/guides/using-react-hooks.md
Normal file
0
data/guides/using-react-hooks.md
Normal file
@ -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": [
|
||||||
@ -38,4 +38,4 @@
|
|||||||
"2017"
|
"2017"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
6
data/site.json
Normal file
6
data/site.json
Normal file
@ -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() {
|
||||||
@ -17,4 +17,4 @@ class GuideLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GuideLayout;
|
export default GuideLayout;
|
||||||
|
@ -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) || {};
|
||||||
|
32
lib/guide.js
Normal file
32
lib/guide.js
Normal file
@ -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;
|
||||||
}
|
}
|
||||||
@ -29,4 +24,4 @@ export const getRequestedRoadmap = req => {
|
|||||||
version: foundVersion,
|
version: foundVersion,
|
||||||
picture: (foundRoadmap.picture || '').replace('{version}', foundVersion),
|
picture: (foundRoadmap.picture || '').replace('{version}', foundVersion),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await callback(props)
|
return await callback(props)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
50
lib/url.js
Normal file
50
lib/url.js
Normal file
@ -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)$/,
|
||||||
@ -58,4 +56,4 @@ let nextConfig = withSass(options);
|
|||||||
nextConfig = withCSS(nextConfig);
|
nextConfig = withCSS(nextConfig);
|
||||||
nextConfig = withMDX(nextConfig);
|
nextConfig = withMDX(nextConfig);
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
10
package.json
10
package.json
@ -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,3 +1,3 @@
|
|||||||
import Roadmap from './index';
|
import Roadmap from './index';
|
||||||
|
|
||||||
export default Roadmap;
|
export default Roadmap;
|
@ -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) {
|
||||||
@ -26,4 +26,4 @@ Roadmap.getInitialProps = serverOnlyProps(({ req }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Roadmap;
|
export default 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>
|
||||||
@ -13,4 +13,4 @@ const About = () => (
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default About;
|
export default About;
|
||||||
|
@ -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>
|
||||||
@ -10,4 +10,4 @@ const Roadmap = () => (
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Roadmap;
|
export default Roadmap;
|
||||||
|
@ -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>
|
||||||
@ -109,4 +109,4 @@ const Privacy = () => (
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Privacy;
|
export default Privacy;
|
||||||
|
13
pages/roadmaps.js
Normal file
13
pages/roadmaps.js
Normal file
@ -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>
|
||||||
@ -162,4 +162,4 @@ const Terms = () => (
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Terms;
|
export default Terms;
|
||||||
|
87
path-map.js
Normal file
87
path-map.js
Normal file
@ -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,
|
||||||
|
};
|
Reference in New Issue
Block a user