Compare commits
121 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
68950ab2c9 | ||
|
305d0a41ac | ||
|
e43c21a01d | ||
|
b6205af02c | ||
|
6de2867d8a | ||
|
13612323d7 | ||
|
edd10470a7 | ||
|
d5b8d761d5 | ||
|
9f7119694b | ||
|
8fbde17c22 | ||
|
e16947bd78 | ||
|
33af054161 | ||
|
8913d5c5e4 | ||
|
0040d568b1 | ||
|
706070e42a | ||
|
f79fb62ff9 | ||
|
5c428540dc | ||
|
35ae0a74b3 | ||
|
a1606521d4 | ||
|
6545c8de36 | ||
|
0f713cbfd8 | ||
|
333894b75b | ||
|
c73a500ffd | ||
|
a489bc0fde | ||
|
09ef6bfbb0 | ||
|
67d2f5cb57 | ||
|
b28deab192 | ||
|
290a73c8b0 | ||
|
579d39e104 | ||
|
e7c32958c9 | ||
|
4a31a757f1 | ||
|
c7a141c024 | ||
|
b4042cb183 | ||
|
0d937384a4 | ||
|
3ee4d49d05 | ||
|
bc9e58c155 | ||
|
0a6d72f671 | ||
|
dd43969dfb | ||
|
0067cfdbc6 | ||
|
6bb1505db3 | ||
|
7e0a392a87 | ||
|
1c94fb8b95 | ||
|
f03472127c | ||
|
d5b091a5ed | ||
|
ba1b876e14 | ||
|
af8a79c48d | ||
|
6ba22799cd | ||
|
63b297ca34 | ||
|
929e6c0a51 | ||
|
9b97fce431 | ||
|
ad427eceee | ||
|
1b9bb03cde | ||
|
68fc45104d | ||
|
96e02195a9 | ||
|
e800e3b6ce | ||
|
ac984346e8 | ||
|
afa0e032e7 | ||
|
86f599b5b7 | ||
|
75c4f952dd | ||
|
aa8aea9433 | ||
|
93797cc5ee | ||
|
fc159ecb9b | ||
|
c097017520 | ||
|
ec1725e312 | ||
|
67b8af3f6b | ||
|
74ef38cdb6 | ||
|
0e90d53b8e | ||
|
ec0cd4692c | ||
|
964a87f605 | ||
|
7c3c552ff1 | ||
|
810cea40b3 | ||
|
ae463059dd | ||
|
3f2ef9ae48 | ||
|
8d05031e51 | ||
|
2c0d1ef77f | ||
|
3a7099bebe | ||
|
8bb36c1451 | ||
|
d3ee830e26 | ||
|
8013c64eb5 | ||
|
3bdac7c7d9 | ||
|
101aed8257 | ||
|
d1bdcfd1da | ||
|
0a3fd7b894 | ||
|
1c065f563d | ||
|
85ac2162bc | ||
|
fe34e7f8d7 | ||
|
13817a4318 | ||
|
197f1f6824 | ||
|
c37297f822 | ||
|
a85497d7e7 | ||
|
4737dd8390 | ||
|
309731fe1b | ||
|
8902100baa | ||
|
bdee14009b | ||
|
55f7d00e83 | ||
|
4459b7d062 | ||
|
c188fc57ae | ||
|
ef6709d2bf | ||
|
51a5f570fe | ||
|
74791e1f7d | ||
|
7d27cb0b9f | ||
|
6dbf170076 | ||
|
461c9a03a7 | ||
|
41ec8327c4 | ||
|
c8395aa3df | ||
|
d80327c8e8 | ||
|
b4ce36fb04 | ||
|
c84e7d41bb | ||
|
14c03912f9 | ||
|
0028d4f80c | ||
|
46f1034163 | ||
|
c44fc62e8a | ||
|
7a6a8c94f9 | ||
|
cadf9ba4a7 | ||
|
4428e8d82b | ||
|
b080ed64ba | ||
|
3bfd06d704 | ||
|
0d21be3796 | ||
|
f15e7a18b8 | ||
|
9c81d818ed | ||
|
5cf17a3e0f |
13
.babelrc
13
.babelrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"next/babel"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"styled-components",
|
||||
{
|
||||
"ssr": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
# EditorConfig for the node-soap library - head over to editorconfig.org to see if you editor supports this file.
|
||||
|
||||
# this is the topmost .editorconfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.xml]
|
||||
insert_final_newline = false
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{package.json, .travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
18
.eslintrc
Normal file
18
.eslintrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": [
|
||||
"next",
|
||||
"next/core-web-vitals",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@next/next/no-img-element": [
|
||||
"off"
|
||||
],
|
||||
"react/display-name": [
|
||||
"off"
|
||||
],
|
||||
"react/jsx-no-target-blank": [
|
||||
"off"
|
||||
]
|
||||
}
|
||||
}
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: kamranahmedse
|
||||
|
9
.github/workflows/deploy.yml
vendored
9
.github/workflows/deploy.yml
vendored
@@ -14,15 +14,14 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 14
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
npm install -g yarn
|
||||
yarn install
|
||||
npm install
|
||||
- name: Generate meta and builld
|
||||
run: |
|
||||
yarn meta
|
||||
yarn build
|
||||
npm run meta
|
||||
npm run build
|
||||
- name: Deploy to GitHub Pages
|
||||
run: |
|
||||
git config user.email "kamranahmed.se@gmail.com"
|
||||
|
48
.gitignore
vendored
48
.gitignore
vendored
@@ -1,14 +1,36 @@
|
||||
_*
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
.idea
|
||||
.next
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
out
|
||||
.env
|
||||
build
|
||||
node_modules
|
||||
yarn-error.log
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
config/*.json
|
||||
!config/dev.json
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.idea
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <kamranahmed.se@gmail.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
20
LICENSE
20
LICENSE
@@ -1,20 +0,0 @@
|
||||
The codebase in this repository is covered under BSD 4-Clause license and
|
||||
the content with CC BY-NC-ND 3.0 with additional clauses
|
||||
|
||||
Everything including text and images in this project are protected by the copyright laws.
|
||||
You are allowed to use this material for personal use but are not allowed to use it for
|
||||
any other purpose including publishing the images, the project files or the content in the
|
||||
images in any form either digital, non-digital, textual, graphical or written formats.
|
||||
You are allowed to share the links to the repository or the website roadmap.sh but not
|
||||
the content for any sort of usage that involves the content of this repository taken out
|
||||
of the repository and be shared from any other medium including but not limited to blog
|
||||
posts, articles, newsletters, you must get prior consent from the understated.
|
||||
|
||||
Copyright © 2021 Kamran Ahmed <kamranahmed.se@gmail.com>
|
||||
|
||||
Please note that I am really flexible with allowing the usage of the content in this
|
||||
repository. If you reach out to me with a brief detail of why and how you would like
|
||||
to use this content, there is a good chance that I will allow you to use it. The reason
|
||||
behind this strictness in the license is to stop the people who have been using these
|
||||
roadmaps in ill manners e.g. ripping people off with suggesting random affiliate links,
|
||||
redistributing these roadmaps just for the sake of monetizing the traffic.
|
@@ -15,7 +15,7 @@
|
||||
</p>
|
||||
</p>
|
||||
|
||||
[roadmap.sh](https://roadmap.sh) is the community effort to create knowledge that is approachable for the developers.
|
||||
[roadmap.sh](https://roadmap.sh) is the community effort to create knowledge that is approachable for the developers.
|
||||
|
||||
The website is built with Next.js, contains roadmaps which are the step by step guides for developers, guides which are the easier to understand explanations on the complex topics. Anyone can contribute to the website by suggesting changes to existing paths, adding learning resources, becoming an author by adding new guides, updating the existing guides.
|
||||
|
@@ -1,22 +0,0 @@
|
||||
import { AboutHeaderWrap } from './style';
|
||||
import siteConfig from "content/site";
|
||||
|
||||
const AboutHeader = () => (
|
||||
<AboutHeaderWrap>
|
||||
<div className="container container-small">
|
||||
<div className="author-info">
|
||||
<img className='author-img d-none d-sm-none d-md-block d-lg-block d-xl-block' src="/kamran.jpeg" />
|
||||
<div className="author-msg">
|
||||
<h1>Hello, I'm Kamran Ahmed.</h1>
|
||||
<p>I created <span className='flow-black'>roadmap.sh</span> to help developers find their path if they are confused and help them grow in their career.</p>
|
||||
<div className="author-links">
|
||||
<a href={`https://twitter.com/${siteConfig.twitter}`} target="_blank">@kamranahmedse</a>
|
||||
<a href="mailto:kamran@roadmap.sh">kamran@roadmap.sh</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AboutHeaderWrap>
|
||||
);
|
||||
|
||||
export default AboutHeader;
|
@@ -1,49 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const AboutHeaderWrap = styled.div`
|
||||
text-align: left;
|
||||
padding: 70px 0;
|
||||
margin: 0 auto;
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.author-img {
|
||||
height: 160px;
|
||||
margin-right: 25px;
|
||||
margin-top: 8px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.author-links a {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
background: #2d2d2d;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
margin-top: 12px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
@@ -1,54 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BadgesList = styled.p`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const PrimaryBadge = styled.span`
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
background: #2929ff;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
|
||||
svg {
|
||||
height: 11px !important;
|
||||
margin-right: 5px;
|
||||
|
||||
&.fa-clock {
|
||||
height: 10px !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SecondaryBadge = styled(PrimaryBadge)`
|
||||
background: #696969;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const InfoBadge = styled(PrimaryBadge)`
|
||||
background: #039640;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const DarkBadge = styled(PrimaryBadge)`
|
||||
background: #101010;
|
||||
color: white;
|
||||
`;
|
||||
|
||||
export const BadgeLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
}
|
||||
`;
|
60
components/content-page-header.tsx
Normal file
60
components/content-page-header.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Box, Container, Flex, Heading, Image, Link, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
type ContentPageHeaderProps = {
|
||||
formattedDate: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
author?: {
|
||||
name: string;
|
||||
twitter: string;
|
||||
picture: string;
|
||||
},
|
||||
subLink?: {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
};
|
||||
|
||||
export function ContentPageHeader(props: ContentPageHeaderProps) {
|
||||
const { title, subtitle, author = null, formattedDate, subLink = null } = props;
|
||||
|
||||
return (
|
||||
<Box pt={['35px', '35px', '70px']} pb={['35px', '35px', '55px']} borderBottomWidth={1} mb='30px'>
|
||||
<Container maxW='container.md' position='relative' textAlign={['left', 'left', 'center']}>
|
||||
<Flex alignItems='center' justifyContent={['flex-start', 'flex-start', 'center']}
|
||||
fontSize={['12px', '12px', '14px']}>
|
||||
|
||||
{author?.name && (
|
||||
<>
|
||||
<Link
|
||||
d={['none', 'flex', 'flex']}
|
||||
target='_blank'
|
||||
href={`https://twitter.com/${author.twitter}`}
|
||||
alignItems='center'
|
||||
fontWeight={600}
|
||||
color='gray.500'
|
||||
>
|
||||
<Image alt={''} rounded={'full'} mr='7px' w='22px' src={author.picture} />
|
||||
{author.name}
|
||||
</Link>
|
||||
<Text d={['none', 'inline', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text color='gray.500' as='span'>{formattedDate}</Text>
|
||||
{subLink?.text && (
|
||||
<>
|
||||
<Text d={['none', 'none', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
<Link d={['none', 'none', 'inline']} color='blue.500' fontWeight={500}
|
||||
href={subLink.url} target={'_blank'}>{subLink.text}</Link>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Heading as='h1' color='black' fontSize={['30px', '30px', '45px']} lineHeight={['40px', '40px', '53px']}
|
||||
fontWeight={700} my={['5px', '5px', '10px']}>{title}</Heading>
|
||||
<Text fontSize={['14px', '14px', '16px']} color='gray.700'>{subtitle}</Text>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
37
components/custom-ad.tsx
Normal file
37
components/custom-ad.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id='carbonads'>
|
||||
<span>
|
||||
<span className='carbon-wrap'>
|
||||
<a
|
||||
href='https://freemote.com/strategy?sl=roadmap'
|
||||
className='carbon-img'
|
||||
target='_blank'
|
||||
>
|
||||
<img
|
||||
src='/fm-img.png'
|
||||
alt='FM Logo'
|
||||
height='100'
|
||||
width='130'
|
||||
style={{ maxWidth: '130px', border: 'none' }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href='https://freemote.com/strategy?sl=roadmap'
|
||||
className='carbon-text'
|
||||
target='_blank'
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href='https://github.com/sponsors/kamranahmedse'
|
||||
className='carbon-poweredby'
|
||||
target='_blank'
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,133 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faTwitterSquare, faRedditSquare, faGithubSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
PageHeader,
|
||||
RoadmapMeta,
|
||||
ShareRoadmap,
|
||||
Sidebar,
|
||||
Summary,
|
||||
SummaryContainer,
|
||||
MobileNavHeader,
|
||||
SidebarButton,
|
||||
MobileSidebar,
|
||||
MobileSidebarWrap,
|
||||
DesktopSidebarWrap,
|
||||
PageTitle,
|
||||
PageDetail
|
||||
} from './style';
|
||||
import { faBars } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getFacebookShareUrl } from 'lib/url';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
import { getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import siteConfig from "content/site";
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const DetailedRoadmap = ({ roadmap }) => {
|
||||
const [menuActive, setMenuState] = useState(false);
|
||||
const {
|
||||
sidebar = {},
|
||||
page: currentPage = {},
|
||||
author = {}
|
||||
} = roadmap;
|
||||
|
||||
const roadmapPages = Object.keys(sidebar || {}).map((groupTitle, groupCounter) => {
|
||||
if (groupTitle.startsWith('_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo remove it after completing the frontend roadmap
|
||||
const isInProgress = groupCounter !== 0;
|
||||
|
||||
return (
|
||||
<div className={`links-group ${isInProgress ? 'in-progress' : ''}`} key={groupTitle}>
|
||||
<h3>
|
||||
{ groupTitle }
|
||||
{ isInProgress && <span className='badge badge-warning progress-badge'>In Progress</span> }
|
||||
</h3>
|
||||
<ul>
|
||||
{ sidebar[groupTitle].map(page => {
|
||||
const isActivePage = page.url === currentPage.url;
|
||||
// e.g. /frontend should mark `/frontend/landscape` as active
|
||||
const isSummaryPage = page.url === `${currentPage.url}/summary`;
|
||||
|
||||
return (
|
||||
<li className={classNames({ active: isActivePage || isSummaryPage })} key={page.url}>
|
||||
<a href={ page.url }>
|
||||
<span className="bullet"></span>
|
||||
{ page.title }
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}) }
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const filePath = currentPage.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<PageHeader className="border-top border-bottom text-center text-md-left">
|
||||
<div className="container d-flex align-items-center flex-column flex-md-row">
|
||||
<RoadmapMeta>
|
||||
<h3>{ roadmap.title }</h3>
|
||||
<p>
|
||||
Roadmap contributed by <a href={ author.url } target="_blank">{ author.name }</a>
|
||||
{ roadmap.contributorsCount > 1 && ` and <a href="${roadmap.contributorsUrl}">${roadmap.contributorsCount} others</a>`}
|
||||
</p>
|
||||
</RoadmapMeta>
|
||||
<ShareRoadmap className="mt-2 mt-md-0">
|
||||
<ShareIcon href={ siteConfig.url.repo } target="_blank">
|
||||
<FontAwesomeIcon icon={ faGithubSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getTwitterShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: roadmap.title, url: roadmap.url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareRoadmap>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<MobileNavHeader className="border-bottom d-block d-md-none">
|
||||
<div className="container">
|
||||
<SidebarButton onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
|
||||
<FontAwesomeIcon icon={ faBars } />
|
||||
{ currentPage.title }
|
||||
</SidebarButton>
|
||||
</div>
|
||||
<MobileSidebarWrap className={classNames({ visible: menuActive })}>
|
||||
<div className="container">
|
||||
<MobileSidebar>
|
||||
{ roadmapPages }
|
||||
</MobileSidebar>
|
||||
</div>
|
||||
</MobileSidebarWrap>
|
||||
</MobileNavHeader>
|
||||
|
||||
<Summary className="container">
|
||||
<DesktopSidebarWrap className="d-none d-md-block">
|
||||
<Sidebar>
|
||||
{ roadmapPages }
|
||||
</Sidebar>
|
||||
</DesktopSidebarWrap>
|
||||
<PageDetail>
|
||||
<PageTitle>{ currentPage.title }</PageTitle>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
</PageDetail>
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default DetailedRoadmap;
|
@@ -1,199 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const Summary = styled.div`
|
||||
text-align: left;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageHeader = styled.div`
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
color: #696969;
|
||||
|
||||
a {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RoadmapMeta = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ShareRoadmap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
margin-bottom: 0;
|
||||
& + a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavHeader = styled.div`
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const SidebarButton = styled.button`
|
||||
background: transparent;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
-webkit-appearance: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PageDetail = styled.div`
|
||||
padding: 25px 0 100px;
|
||||
`;
|
||||
|
||||
export const PageTitle = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
export const Sidebar = styled.div`
|
||||
padding-bottom: 150px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.links-group {
|
||||
padding: 30px 0 10px;
|
||||
width: 260px;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links-group.in-progress {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.links-group li {
|
||||
list-style: none;
|
||||
margin: 7px 0;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
min-width: 7px;
|
||||
border-radius: 100%;
|
||||
background: #efefef;
|
||||
transform: translateX(-4px);
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
&.active .bullet {
|
||||
background: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DesktopSidebarWrap = styled.div`
|
||||
border-left: 1px solid #efefef;
|
||||
|
||||
${Sidebar} {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebarWrap = styled.div`
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
background: white;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 100%;
|
||||
overflow-y: scroll;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 20px;
|
||||
top: calc(100% + 1px);
|
||||
transition: bottom 0.5s ease 0s;
|
||||
|
||||
&.visible {
|
||||
bottom: -50vh;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileSidebar = styled(Sidebar)`
|
||||
border-left: 1px solid #efefef;
|
||||
margin-left: 12px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.links-group {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.progress-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
`;
|
46
components/dimmed-more.tsx
Normal file
46
components/dimmed-more.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type DimmedMoreProps = {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export function DimmedMore(props: DimmedMoreProps) {
|
||||
const { text, href } = props;
|
||||
|
||||
return (
|
||||
<Box position='relative' textAlign='center' bottom='20px'>
|
||||
<Box
|
||||
opacity={1}
|
||||
pointerEvents='none'
|
||||
position='absolute'
|
||||
bottom={0}
|
||||
height='200px'
|
||||
width='100%'
|
||||
background='linear-gradient(180deg, rgb(255 255 255 / 40%), white)'
|
||||
/>
|
||||
|
||||
<Link
|
||||
rounded='20px'
|
||||
display='inline'
|
||||
bg='green.600'
|
||||
color='white'
|
||||
p='7px 20px'
|
||||
href={href}
|
||||
fontWeight={800}
|
||||
fontSize='11px'
|
||||
textTransform='uppercase'
|
||||
my='25px'
|
||||
position='relative'
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
'& .forward-arrow': {
|
||||
transform: 'translateX(3px)'
|
||||
}
|
||||
}}>
|
||||
{text}
|
||||
<Text d='inline-block' as='span' transition='200ms' ml='4px' className='forward-arrow'>→</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import { FaqContainer } from './style';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const AboutPage = require(`../../content/pages/about.md`).default;
|
||||
|
||||
const FaqList = () => (
|
||||
<FaqContainer className='border-top bg-light'>
|
||||
<div className="container container-small">
|
||||
<MdRenderer>
|
||||
<AboutPage />
|
||||
</MdRenderer>
|
||||
</div>
|
||||
</FaqContainer>
|
||||
);
|
||||
|
||||
export default FaqList;
|
@@ -1,25 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FaqContainer = styled.div`
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
margin-top: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
}
|
||||
`;
|
@@ -1,25 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import FeaturedGuide from 'components/featured-guide';
|
||||
import { getFeaturedGuides } from 'lib/guide';
|
||||
|
||||
const FeaturedGuides = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<p className='border-through featured-separator'>
|
||||
<span>
|
||||
Guides mostly visited by the community
|
||||
<a href="/guides" className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Guides →</a>
|
||||
</span>
|
||||
</p>
|
||||
<div className="swim-lane row">
|
||||
{ getFeaturedGuides()
|
||||
.map(guide => (
|
||||
<FeaturedGuide guide={ guide } key={ guide.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedGuides;
|
@@ -1,16 +0,0 @@
|
||||
import { FeaturedWrap } from './style';
|
||||
import FeaturedGuides from './guides';
|
||||
import FeaturedRoadmaps from './roadmaps';
|
||||
|
||||
const FeaturedContent = (props) => (
|
||||
<FeaturedWrap className="border-top bg-light">
|
||||
<FeaturedRoadmaps />
|
||||
<FeaturedGuides />
|
||||
</FeaturedWrap>
|
||||
);
|
||||
|
||||
FeaturedContent.defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default FeaturedContent;
|
@@ -1,27 +0,0 @@
|
||||
import { FeaturedContentWrap } from './style';
|
||||
import roadmaps from 'content/roadmaps';
|
||||
import FeaturedRoadmap from 'components/featured-roadmap';
|
||||
|
||||
const FeaturedRoadmaps = () => (
|
||||
<FeaturedContentWrap className="featured-content-wrap">
|
||||
<div className="container">
|
||||
<div className="featured-head">
|
||||
<p className="border-through featured-separator">
|
||||
<span>
|
||||
Roadmaps mostly visited by the community
|
||||
<a href='/roadmaps' className="dark-link d-none d-sm-none d-md-inline d-xl-inline">View all Roadmaps →</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="swim-lane row">
|
||||
{ roadmaps
|
||||
.filter(({ featured }) => featured)
|
||||
.map(roadmap => (
|
||||
<FeaturedRoadmap roadmap={ roadmap } key={ roadmap.url } />
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
</FeaturedContentWrap>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmaps;
|
@@ -1,110 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FeaturedWrap = styled.div`
|
||||
padding: 30px 0 50px;
|
||||
`;
|
||||
|
||||
export const FeaturedContentWrap = styled.div`
|
||||
.featured-head {
|
||||
text-align: center;
|
||||
display: block;
|
||||
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
font-size: 35px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.featured-separator {
|
||||
font-size: 16px;
|
||||
margin-bottom: 40px;
|
||||
margin-top: 25px;
|
||||
color: #999;
|
||||
|
||||
span {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
a {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.swim-lane {
|
||||
.featured-block {
|
||||
}
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
padding: 15px 10px;
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.journey-block {
|
||||
a {
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
|
||||
img {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.journey-meta {
|
||||
padding: 18px 25px 20px;
|
||||
|
||||
h4 {
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
filter: grayscale(1);
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
}
|
||||
`;
|
@@ -1,25 +0,0 @@
|
||||
import formatDate from 'date-fns/format';
|
||||
|
||||
import { Author, AuthorImage, AuthorName, BlockLink, BlockMeta, BlockSubtitle, BlockTitle, PublishDate } from './style';
|
||||
import { findByUsername } from 'lib/author';
|
||||
|
||||
const FeaturedGuide = ({ guide }) => {
|
||||
const author = findByUsername(guide.author) || {};
|
||||
return (
|
||||
<div className='col-xl-4 col-lg-6 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={guide.url}>
|
||||
<BlockTitle>{guide.title}</BlockTitle>
|
||||
<BlockSubtitle>{guide.featuredDescription || guide.description}</BlockSubtitle>
|
||||
<BlockMeta>
|
||||
<Author>
|
||||
<AuthorImage src={author.picture} />
|
||||
<AuthorName>{author.name}</AuthorName>
|
||||
</Author>
|
||||
<PublishDate>{formatDate(new Date(guide.createdAt), 'MMMM d, yyyy')}</PublishDate>
|
||||
</BlockMeta>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturedGuide;
|
@@ -1,65 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 25px 25px 22px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
export const BlockMeta = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 19px;
|
||||
`;
|
||||
|
||||
export const PublishDate = styled.time`
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const Author = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
export const AuthorName = styled.div``;
|
@@ -1,12 +0,0 @@
|
||||
import { BlockLink, BlockSubtitle, BlockTitle } from './style';
|
||||
|
||||
const FeaturedRoadmap = ({ roadmap }) => (
|
||||
<div className='col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12 grid-item-container'>
|
||||
<BlockLink href={roadmap.url}>
|
||||
<BlockTitle>{roadmap.title}</BlockTitle>
|
||||
<BlockSubtitle>{roadmap.featuredDescription || roadmap.description}</BlockSubtitle>
|
||||
</BlockLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FeaturedRoadmap;
|
@@ -1,38 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BlockLink = styled.a`
|
||||
border: 1px solid #f7f7f7;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
padding: 26px 25px 25px;
|
||||
border-radius: 10px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
cursor: pointer;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockTitle = styled.h4`
|
||||
line-height: 27px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const BlockSubtitle = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 25px;
|
||||
color: #999999;
|
||||
margin-bottom: 0;
|
||||
`;
|
79
components/footer.tsx
Normal file
79
components/footer.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Box, Container, Flex, Image, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import siteConfig from '../content/site.json';
|
||||
import { CustomAd } from './custom-ad';
|
||||
|
||||
function NavigationLinks() {
|
||||
return (
|
||||
<>
|
||||
<Stack isInline d={['none', 'none', 'flex']} color='gray.400' fontWeight={600} spacing='30px'>
|
||||
<Link _hover={{ color: 'white' }} href='/roadmaps'>Roadmaps</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/guides'>Guides</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/watch'>Videos</Link>
|
||||
<Link _hover={{ color: 'white' }} href='/about'>About</Link>
|
||||
<Link _hover={{ color: 'white' }} href={siteConfig.url.youtube} target='_blank'>YouTube</Link>
|
||||
</Stack>
|
||||
|
||||
<Stack d={['flex', 'flex', 'none']} color='gray.400' fontWeight={600} spacing={0}>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/roadmaps'>Roadmaps</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/guides'>Guides</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/watch'>Videos</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/thanks'>Thanks</Link>
|
||||
<Link py='7px' borderBottomWidth={1} borderBottomColor='gray.800' _hover={{ color: 'white' }}
|
||||
href='/about'>About</Link>
|
||||
<Link py='7px' _hover={{ color: 'white' }} target='_blank'
|
||||
href={siteConfig.url.youtube}>YouTube</Link>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<Box bg='gray.900' p={['25px 0', '25px 0', '40px 0']}>
|
||||
<Container maxW='container.md'>
|
||||
<NavigationLinks />
|
||||
|
||||
<Box mt={['40px', '40px', '50px']} mb='40px' maxW='500px'>
|
||||
<Flex spacing={0} alignItems='center' color='gray.400'>
|
||||
<Link d='flex' alignItems='center' fontWeight={600} _hover={{ textDecoration: 'none', color: 'white' }}
|
||||
href='/'>
|
||||
<Image alt='' h='25px' w='25px' src='/logo.svg' mr='6px' />
|
||||
roadmap.sh
|
||||
</Link>
|
||||
<Text as='span' mx='7px'>by</Text>
|
||||
<Link bg='blue.500' px='6px' py='2px' rounded='4px' color='white' fontWeight={600} fontSize='13px'
|
||||
_hover={{ textDecoration: 'none', bg: 'blue.600' }} href={siteConfig.url.twitter}
|
||||
target='_blank'>@kamranahmedse</Link>
|
||||
</Flex>
|
||||
|
||||
<Text my='15px' fontSize='14px' color='gray.500'>Community created roadmaps, articles, resources and
|
||||
journeys to help you choose your path and grow in your career.</Text>
|
||||
|
||||
<Text fontSize='14px' color='gray.500'>
|
||||
<Text as='span' mr='10px'>© roadmap.sh</Text>·
|
||||
<Link href='/about' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>FAQs</Link>·
|
||||
<Link href='/terms' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Terms</Link>·
|
||||
<Link href='/privacy' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Privacy</Link>
|
||||
</Text>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<CustomAd />
|
||||
{process.env.GA_SECRET && false && (
|
||||
<script
|
||||
async
|
||||
type='text/javascript'
|
||||
src='//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh'
|
||||
id='_carbonads_js'
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
113
components/global-header.tsx
Normal file
113
components/global-header.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useState } from 'react';
|
||||
import { HamburgerIcon } from '@chakra-ui/icons';
|
||||
import { Box, CloseButton, Container, Flex, IconButton, Image, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import RoadmapLogo from '../components/icons/roadmap.svg';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
type MenuLinkProps = {
|
||||
text: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
function MenuLink(props: MenuLinkProps) {
|
||||
const { text, link } = props;
|
||||
|
||||
return <Link
|
||||
borderBottomWidth={0}
|
||||
borderBottomColor='gray.500'
|
||||
_hover={{ textDecoration: 'none', borderBottomColor: 'white' }}
|
||||
fontWeight={500}
|
||||
href={link}
|
||||
>
|
||||
{text}
|
||||
</Link>;
|
||||
}
|
||||
|
||||
function DesktopMenuLinks() {
|
||||
return (
|
||||
<Stack d={['none', 'flex', 'flex']} shouldWrapChildren isInline spacing='15px' alignItems='center' color='gray.50'
|
||||
fontSize='15px'>
|
||||
<MenuLink text={'Roadmaps'} link={'/roadmaps'} />
|
||||
<MenuLink text={'Guides'} link={'/guides'} />
|
||||
<MenuLink text={'Videos'} link={'/watch'} />
|
||||
<MenuLink text={'Thanks'} link={'/thanks'} />
|
||||
|
||||
<Link ml='10px' bgGradient='linear(to-l, yellow.700, red.600)' p='7px 10px' rounded='4px'
|
||||
_hover={{ textDecoration: 'none', bgGradient: 'linear(to-l, red.800, yellow.700)' }}
|
||||
fontWeight={500} href={'/signup'}>Subscribe</Link>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileMenuLinks() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
rounded='5px'
|
||||
padding={0}
|
||||
aria-label={'Menu'}
|
||||
d={['block', 'none', 'none']}
|
||||
icon={<HamburgerIcon color='white' w='25px' height='25px' />}
|
||||
color='white'
|
||||
cursor='pointer'
|
||||
h='auto'
|
||||
bg='transparent'
|
||||
_hover={{ bg: 'transparent' }}
|
||||
_active={{ bg: 'transparent' }}
|
||||
_focus={{ bg: 'transparent' }}
|
||||
onClick={() => setIsOpen(true)}
|
||||
/>
|
||||
|
||||
{isOpen && (
|
||||
<Stack color='gray.100'
|
||||
fontSize={['22px', '22px', '22px', '32px']}
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
pos='fixed'
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
top={0}
|
||||
bg='gray.900'
|
||||
spacing='12px'
|
||||
zIndex={1}
|
||||
>
|
||||
<Link href='/roadmaps'>Roadmaps</Link>
|
||||
<Link href='/guides'>Guides</Link>
|
||||
<Link href='/watch'>Videos</Link>
|
||||
<Link href='/thanks'>Thanks</Link>
|
||||
<Link href='/signup'>Subscribe</Link>
|
||||
<CloseButton onClick={() => setIsOpen(false)} pos='fixed' top='40px' right='15px' size='lg' />
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function GlobalHeader() {
|
||||
return (
|
||||
<Box bg='gray.900' p='58px 0 20px'>
|
||||
<Container maxW='container.md'>
|
||||
<Flex justifyContent='space-between' alignItems='center'>
|
||||
<Box>
|
||||
<Link w='100%'
|
||||
d='flex'
|
||||
href='/'
|
||||
alignItems='center'
|
||||
color='white'
|
||||
fontWeight={600}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
fontSize='18px'>
|
||||
<RoadmapLogo style={{ height: '30px', width: '30px', marginRight: '10px' }} />
|
||||
<Text d={['block', 'none', 'block']} as='span'>roadmap.sh</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
<DesktopMenuLinks />
|
||||
<MobileMenuLinks />
|
||||
</Flex>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
import MdRenderer from 'components/md-renderer'
|
||||
import SharePage from 'components/share-page';
|
||||
import { GuideBodyWrap } from './style';
|
||||
|
||||
const GuideBody = ({ guide }) => {
|
||||
const GuideContent = require(`../../content/guides/${guide.fileName}.md`).default;
|
||||
return (
|
||||
<GuideBodyWrap>
|
||||
<MdRenderer>
|
||||
<GuideContent />
|
||||
{
|
||||
guide.author && <SharePage
|
||||
title={ guide.title }
|
||||
url={ guide.url }
|
||||
twitterUsername={ guide.author.twitter }
|
||||
/>
|
||||
}
|
||||
</MdRenderer>
|
||||
</GuideBodyWrap>
|
||||
);
|
||||
};
|
||||
|
||||
export default GuideBody;
|
@@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const GuideBodyWrap = styled.div`
|
||||
position: relative;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
min-height: 300px;
|
||||
|
||||
p:first-child, h1, h2, h3, h4, h5, h6, img, blockquote {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
@@ -1,83 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faGithub, faHackerNewsSquare, faRedditSquare, faTwitter, faTwitterSquare } from '@fortawesome/free-brands-svg-icons'
|
||||
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 = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<FooterWrap>
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<ShareWrap>
|
||||
<ContributeIcon>
|
||||
<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-inline d-sm-inline d-md-none d-lg-none d-xl-none">Contribute </span>
|
||||
<FontAwesomeIcon icon={faGithub}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<ContributeIcon hasMargins>
|
||||
<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-inline d-sm-inline d-md-none d-lg-none d-xl-none">Author </span>
|
||||
<FontAwesomeIcon icon={faTwitter}/>
|
||||
</a>
|
||||
</ContributeIcon>
|
||||
<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-inline d-sm-inline d-md-inline d-lg-none d-xl-none">Share</span>
|
||||
<a href={ getTwitterShareUrl({ text: `${guide.title} by @${author.twitter}`, url: guide.url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faTwitterSquare}/>
|
||||
</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>
|
||||
</ShareWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
|
||||
<FooterBg className="border-top">
|
||||
<FooterContainer>
|
||||
<AuthorInfoWrap>
|
||||
<AuthorImg src={ author.picture } alt={ author.name }/>
|
||||
<AuthorMeta>
|
||||
<h4><a href={ getTwitterUrl(author.twitter) } target="_blank">{ author.name }</a></h4>
|
||||
<AuthorBio>{ author.bio }</AuthorBio>
|
||||
</AuthorMeta>
|
||||
</AuthorInfoWrap>
|
||||
</FooterContainer>
|
||||
</FooterBg>
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default GuideFooter;
|
@@ -1,123 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
display: block;
|
||||
margin-top: 50px;
|
||||
`;
|
||||
|
||||
export const FooterContainer = styled.div`
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
`;
|
||||
|
||||
export const FooterBg = styled.div`
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
padding: 17px 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #101010;
|
||||
|
||||
svg {
|
||||
height: 18px !important;
|
||||
width: 18px !important;
|
||||
color: #757575;
|
||||
margin-left: 7px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const ContributeIcon = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: ${props => props.hasMargins ? '0 30px' : '0'};
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const ShareIcons = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #757575;
|
||||
font-size: 14px;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorInfoWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40px 0;
|
||||
|
||||
h4 {
|
||||
position: relative;
|
||||
font-size: 22px;
|
||||
margin: 10px 0;
|
||||
line-height: 17px;
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #101010;
|
||||
}
|
||||
}
|
||||
|
||||
.author-description {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorBio = styled.p`
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
color: #757575;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImg = styled.img`
|
||||
border-radius: 100%;
|
||||
height: 100px !important;
|
||||
width: 100px !important;
|
||||
margin-right: 22px;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const AuthorMeta = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
`;
|
@@ -1,57 +0,0 @@
|
||||
import formatDate from 'date-fns/format'
|
||||
import {
|
||||
ActionItems,
|
||||
AuthorImage,
|
||||
EditGuide,
|
||||
GuideAuthor,
|
||||
GuideDate,
|
||||
GuideMeta,
|
||||
GuideSubtitle,
|
||||
GuideTitle,
|
||||
HeaderWrap,
|
||||
} from './style';
|
||||
import { getContributionUrl } from "lib/guide";
|
||||
import { getTwitterUrl } from "lib/url";
|
||||
import { faClock, faEnvelope, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, PrimaryBadge, SecondaryBadge, DarkBadge } from 'components/badges';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const GuideHeader = ({
|
||||
guide,
|
||||
guide: {
|
||||
author = {}
|
||||
} = {}
|
||||
}) => (
|
||||
<HeaderWrap className="border-bottom">
|
||||
<GuideMeta>
|
||||
<GuideAuthor href={ getTwitterUrl(author.twitter) } target="_blank">
|
||||
<AuthorImage src={ author.picture } />
|
||||
{ author.name }
|
||||
</GuideAuthor>
|
||||
·
|
||||
<GuideDate>{ formatDate(new Date(guide.createdAt), 'EEEE, MMMM d yyyy') }</GuideDate>
|
||||
·
|
||||
<EditGuide href={ getContributionUrl(guide) } target="_blank">Improve this Guide</EditGuide>
|
||||
</GuideMeta>
|
||||
<GuideTitle>{ guide.title }</GuideTitle>
|
||||
<GuideSubtitle>{ guide.description }</GuideSubtitle>
|
||||
<ActionItems>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/guides">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||
Other Guides
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon icon={faEnvelope}/>
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
</ActionItems>
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default GuideHeader;
|
@@ -1,54 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
padding: 80px 15px 45px;
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
`;
|
||||
|
||||
export const GuideTitle = styled.h1`
|
||||
font-weight: 700;
|
||||
font-size: 46px;
|
||||
margin: 12px 0;
|
||||
`;
|
||||
|
||||
export const GuideSubtitle = styled.p`
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
`;
|
||||
|
||||
export const GuideMeta = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: #757575;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
`;
|
||||
|
||||
export const GuideDate = styled.span`
|
||||
margin-left: 7px;
|
||||
margin-right: 7px;
|
||||
`;
|
||||
|
||||
export const GuideAuthor = styled.a`
|
||||
margin-right: 7px;
|
||||
font-weight: 500;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthorImage = styled.img`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 100%;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
export const EditGuide = styled.a`
|
||||
margin-left: 7px;
|
||||
`;
|
||||
|
||||
export const ActionItems = styled.div``;
|
31
components/guide/guide-grid-item.tsx
Normal file
31
components/guide/guide-grid-item.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Badge, Box, Heading, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type GuideGridItemProps = {
|
||||
title: string;
|
||||
href: string;
|
||||
subtitle: string;
|
||||
date: string;
|
||||
isNew?: boolean;
|
||||
colorIndex?: number;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'gray.700',
|
||||
'purple.800'
|
||||
];
|
||||
|
||||
export function GuideGridItem(props: GuideGridItemProps) {
|
||||
const { title, subtitle, date, isNew = false, colorIndex = 0, href } = props;
|
||||
|
||||
return (
|
||||
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={href} shadow='xl' p='20px'
|
||||
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1}>
|
||||
<Text mb='10px' fontSize='13px' color='gray.400'>
|
||||
{isNew && <Badge colorScheme={'yellow'} mr='10px'>New</Badge>}
|
||||
{date}
|
||||
</Text>
|
||||
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
|
||||
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
73
components/helmet.tsx
Normal file
73
components/helmet.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
type HelmetProps = {
|
||||
title?: string;
|
||||
keywords?: string[];
|
||||
canonical?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
const Helmet = (props: HelmetProps) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
|
||||
<title>{props.title || siteConfig.title}</title>
|
||||
<meta name='description' content={props.description || siteConfig.description} />
|
||||
|
||||
<meta name='author' content={siteConfig.author} />
|
||||
<meta name='keywords' content={props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',')} />
|
||||
|
||||
<meta name='viewport'
|
||||
content='width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0' />
|
||||
{props.canonical && <link rel='canonical' href={props.canonical} />}
|
||||
<meta httpEquiv='Content-Language' content='en' />
|
||||
|
||||
<meta property='og:title' content={props.title || siteConfig.title} />
|
||||
<meta property='og:description' content={props.description || siteConfig.description} />
|
||||
<meta property='og:image' content={`${siteConfig.url.web}${siteConfig.logoSquare}`} />
|
||||
<meta property='og:url' content={siteConfig.url.web} />
|
||||
<meta property='og:type' content='website' />
|
||||
<meta property='article:publisher' content={`https://facebook.com/${siteConfig.facebook}`} />
|
||||
<meta property='og:site_name' content={siteConfig.name} />
|
||||
<meta property='article:author' content={siteConfig.author} />
|
||||
|
||||
<meta name='twitter:card' content='summary' />
|
||||
<meta name='twitter:site' content={`@${siteConfig.twitter}`} />
|
||||
<meta name='twitter:title' content={props.title || siteConfig.title} />
|
||||
<meta name='twitter:description' content={props.description || siteConfig.description} />
|
||||
<meta name='twitter:image' content={`${siteConfig.url.web}${siteConfig.logoSquare}`} />
|
||||
<meta name='twitter:image:alt' content='roadmap.sh' />
|
||||
|
||||
<meta name='mobile-web-app-capable' content='yes' />
|
||||
<meta name='apple-mobile-web-app-capable' content='yes' />
|
||||
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/manifest/apple-touch-icon.png' />
|
||||
<meta name='msapplication-TileColor' content='#101010' />
|
||||
<meta name='theme-color' content='#848a9a' />
|
||||
|
||||
<link rel='manifest' href='/manifest/manifest.json' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='/manifest/icon32.png' />
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='/manifest/icon16.png' />
|
||||
<link rel='shortcut icon' href='/manifest/favicon.ico' type='image/x-icon' />
|
||||
<link rel='icon' href='/manifest/favicon.ico' type='image/x-icon' />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */}
|
||||
{process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}`} />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
export default Helmet;
|
@@ -1,74 +0,0 @@
|
||||
import NextHead from 'next/head';
|
||||
import siteConfig from 'content/site';
|
||||
|
||||
const prepareTitle = (givenTitle) => {
|
||||
return givenTitle || siteConfig.title;
|
||||
};
|
||||
|
||||
const prepareDescription = (givenDescription) => {
|
||||
return givenDescription || siteConfig.description;
|
||||
};
|
||||
|
||||
// noinspection JSUnresolvedLibraryURL
|
||||
const Helmet = (props) => (
|
||||
<NextHead>
|
||||
<meta charSet='UTF-8' />
|
||||
|
||||
<title>{ prepareTitle(props.title) }</title>
|
||||
<meta name='description' content={ prepareDescription(props.description) } />
|
||||
|
||||
<meta name="author" content={ siteConfig.author } />
|
||||
<meta name="keywords" content={ props.keywords ? props.keywords.join(',') : siteConfig.keywords.join(',') } />
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0" />
|
||||
{ props.canonical && <link rel="canonical" href={ props.canonical } /> }
|
||||
<meta httpEquiv="Content-Language" content="en" />
|
||||
|
||||
<meta property="og:title" content={ prepareTitle(props.title) } />
|
||||
<meta property="og:description" content={ prepareDescription(props.description) } />
|
||||
<meta property="og:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta property="og:url" content={ siteConfig.url.web } />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="article:publisher" content={ `https://facebook.com/${siteConfig.facebook}` } />
|
||||
<meta property="og:site_name" content={ siteConfig.name } />
|
||||
<meta property="article:author" content={ siteConfig.author } />
|
||||
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content={ `@${siteConfig.twitter}` } />
|
||||
<meta name="twitter:title" content={ prepareTitle(props.title) } />
|
||||
<meta name="twitter:description" content={ prepareDescription(props.description) } />
|
||||
<meta name="twitter:image" content={ `${siteConfig.url.web}${siteConfig.logoSquare}` } />
|
||||
<meta name="twitter:image:alt" content="roadmap.sh" />
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/manifest/apple-touch-icon.png" />
|
||||
<meta name="msapplication-TileColor" content="#101010" />
|
||||
<meta name="theme-color" content="#848a9a" />
|
||||
|
||||
<link rel="manifest" href="/manifest/manifest.json" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/manifest/icon32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/manifest/icon16.png" />
|
||||
<link rel="shortcut icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/manifest/favicon.ico" type="image/x-icon" />
|
||||
|
||||
{ /* Global Site Tag (gtag.js) - Google Analytics */ }
|
||||
{ process.env.GA_SECRET && (
|
||||
<>
|
||||
<script async src={ `https://www.googletagmanager.com/gtag/js?id=${process.env.GA_SECRET}` } />
|
||||
<script dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${process.env.GA_SECRET}');
|
||||
`,
|
||||
}} />
|
||||
</>
|
||||
)}
|
||||
|
||||
</NextHead>
|
||||
);
|
||||
|
||||
export default Helmet;
|
@@ -1,13 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
import { HeroSectionWrap } from './style';
|
||||
|
||||
const HeroSection = () => (
|
||||
<HeroSectionWrap>
|
||||
<div className="container">
|
||||
<h1>Developer Roadmaps</h1>
|
||||
<p>Community driven roadmaps, articles and resources for developers. <Link href="/signup"><a>Sign up</a></Link> for occasional updates on new roadmaps, updates and guides</p>
|
||||
</div>
|
||||
</HeroSectionWrap>
|
||||
);
|
||||
|
||||
export default HeroSection;
|
@@ -1,30 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeroSectionWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 70px 20px;
|
||||
margin: 0 auto;
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 70px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
}
|
||||
`;
|
@@ -1,15 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import RowBlock from 'components/row-block';
|
||||
import { SubmitText, SubmitWrap } from './style';
|
||||
|
||||
const IconRowBlock = ({ url, icon, text, openExternal=false }) => (
|
||||
<RowBlock url={ url } openExternal={openExternal}>
|
||||
<SubmitWrap>
|
||||
<FontAwesomeIcon icon={ icon } />
|
||||
<SubmitText>{ text }</SubmitText>
|
||||
</SubmitWrap>
|
||||
</RowBlock>
|
||||
);
|
||||
|
||||
export default IconRowBlock;
|
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SubmitWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
height: 25px;
|
||||
color: #d6d6d6;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SubmitText = styled.p`
|
||||
margin-bottom: 0;
|
||||
color: dimgrey;
|
||||
margin-top: 8px;
|
||||
`;
|
@@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="GitHub" role="img" viewBox="0 0 512 512" width="22px" height="20px">
|
||||
<rect width="512" height="512" rx="15%"/>
|
||||
<path fill="#fff" d="M335 499c14 0 12 17 12 17H165s-2-17 12-17c13 0 16-6 16-12l-1-50c-71 16-86-28-86-28-12-30-28-37-28-37-24-16 1-16 1-16 26 2 40 26 40 26 22 39 59 28 74 22 2-17 9-28 16-35-57-6-116-28-116-126 0-28 10-51 26-69-3-6-11-32 3-67 0 0 21-7 70 26 42-12 86-12 128 0 49-33 70-26 70-26 14 35 6 61 3 67 16 18 26 41 26 69 0 98-60 120-117 126 10 8 18 24 18 48l-1 70c0 6 3 12 16 12z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 841 B |
4
components/icons/roadmap.svg
Normal file
4
components/icons/roadmap.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 283 283" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 39C0 17.4609 17.4609 0 39 0H244C265.539 0 283 17.4609 283 39V244C283 265.539 265.539 283 244 283H39C17.4609 283 0 265.539 0 244V39Z" fill="black"></path>
|
||||
<path d="M121.215 210.72C119.348 211.28 116.361 211.84 112.255 212.4C108.335 212.96 104.228 213.24 99.9347 213.24C95.828 213.24 92.0947 212.96 88.7347 212.4C85.5614 211.84 82.8547 210.72 80.6147 209.04C78.3747 207.36 76.6014 205.12 75.2947 202.32C74.1747 199.333 73.6147 195.507 73.6147 190.84V106.84C73.6147 102.547 74.3614 98.9067 75.8547 95.92C77.5347 92.7467 79.868 89.9467 82.8547 87.52C85.8414 85.0933 89.4814 82.9467 93.7747 81.08C98.2547 79.0267 103.015 77.2533 108.055 75.76C113.095 74.2667 118.321 73.1467 123.735 72.4C129.148 71.4667 134.561 71 139.975 71C148.935 71 156.028 72.7733 161.255 76.32C166.481 79.68 169.095 85.28 169.095 93.12C169.095 95.7333 168.721 98.3467 167.975 100.96C167.228 103.387 166.295 105.627 165.175 107.68C161.255 107.68 157.241 107.867 153.135 108.24C149.028 108.613 145.015 109.173 141.095 109.92C137.175 110.667 133.441 111.507 129.895 112.44C126.535 113.187 123.641 114.12 121.215 115.24V210.72ZM166.387 188.32C166.387 180.48 168.813 173.947 173.667 168.72C178.52 163.493 185.147 160.88 193.547 160.88C201.947 160.88 208.573 163.493 213.427 168.72C218.28 173.947 220.707 180.48 220.707 188.32C220.707 196.16 218.28 202.693 213.427 207.92C208.573 213.147 201.947 215.76 193.547 215.76C185.147 215.76 178.52 213.147 173.667 207.92C168.813 202.693 166.387 196.16 166.387 188.32Z" fill="white"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg width="29" height="29">
|
||||
<svg width="29" height="29" fill="currentColor">
|
||||
<path d="M22.05 7.54a4.47 4.47 0 0 0-3.3-1.46 4.53 4.53 0 0 0-4.53 4.53c0 .35.04.7.08 1.05A12.9 12.9 0 0 1 5 6.89a5.1 5.1 0 0 0-.65 2.26c.03 1.6.83 2.99 2.02 3.79a4.3 4.3 0 0 1-2.02-.57v.08a4.55 4.55 0 0 0 3.63 4.44c-.4.08-.8.13-1.21.16l-.81-.08a4.54 4.54 0 0 0 4.2 3.15 9.56 9.56 0 0 1-5.66 1.94l-1.05-.08c2 1.27 4.38 2.02 6.94 2.02 8.3 0 12.86-6.9 12.84-12.85.02-.24 0-.43 0-.65a8.68 8.68 0 0 0 2.26-2.34c-.82.38-1.7.62-2.6.72a4.37 4.37 0 0 0 1.95-2.51c-.84.53-1.81.9-2.83 1.13z"></path>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 550 B |
21
components/icons/video-icon.tsx
Normal file
21
components/icons/video-icon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
export function VideoIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
fill='currentColor'
|
||||
strokeWidth='0'
|
||||
viewBox='0 0 24 24'
|
||||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<path fill='none' d='M0 0h24v24H0z' />
|
||||
<path
|
||||
d='M3 3.993C3 3.445 3.445 3 3.993 3h16.014c.548 0 .993.445.993.993v16.014a.994.994 0 0 1-.993.993H3.993A.994.994 0 0 1 3 20.007V3.993zm7.622 4.422a.4.4 0 0 0-.622.332v6.506a.4.4 0 0 0 .622.332l4.879-3.252a.4.4 0 0 0 0-.666l-4.88-3.252z'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
3
components/icons/youtube.svg
Normal file
3
components/icons/youtube.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill='currentColor'>
|
||||
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 369 B |
@@ -1,9 +0,0 @@
|
||||
export function BadgeLink({ target='_blank', variant ='success', badgeText, href, children }) {
|
||||
return (
|
||||
<p className='mb-0'>
|
||||
<a href={href} target={ target }>
|
||||
<span style={{ position: 'relative', top: '-2px'}} className={`badge badge-${variant}`}>{badgeText}</span> { children }
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StrongLink = styled.a`
|
||||
border-bottom: 2px solid currentColor;
|
||||
position: relative;
|
||||
transition: background-color 120ms;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
47
components/links-list-item.tsx
Normal file
47
components/links-list-item.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Badge, Flex, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type LinksListItemProps = {
|
||||
href: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
badgeText?: string;
|
||||
icon?: React.ReactChild;
|
||||
hideSubtitleOnMobile?: boolean;
|
||||
};
|
||||
|
||||
export function LinksListItem(props: LinksListItemProps) {
|
||||
const { title, subtitle, badgeText, icon, hideSubtitleOnMobile = false, href } = props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
fontSize={['14px', '14px', '15px']}
|
||||
py='9px'
|
||||
d='flex'
|
||||
flexDirection={['column', 'row', 'row']}
|
||||
fontWeight={500}
|
||||
color='gray.600'
|
||||
alignItems={['flex-start', 'center']}
|
||||
justifyContent={'space-between'}
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
color: 'blue.400',
|
||||
'& .list-item-title': {
|
||||
transform: 'translateX(10px)'
|
||||
}
|
||||
}}
|
||||
isTruncated
|
||||
maxWidth='100%'
|
||||
>
|
||||
<Flex alignItems='center' className='list-item-title' transition={'200ms'}>
|
||||
{icon}
|
||||
<Text maxWidth={'345px'} isTruncated as='span'>{title}</Text>
|
||||
{badgeText &&
|
||||
<Badge pos='relative' top='1px' variant='subtle' colorScheme='purple' ml='10px'>{badgeText}</Badge>}
|
||||
</Flex>
|
||||
<Text d={[hideSubtitleOnMobile ? 'none' : 'inline', 'inline']} mt={['3px', 0]} as='span'
|
||||
fontSize={['11px', '11px', '12px']} color='gray.500'>{subtitle}</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
21
components/links-list.tsx
Normal file
21
components/links-list.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { StackDivider, VStack } from '@chakra-ui/react';
|
||||
|
||||
type LinksListProps = {
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export function LinksList(props: LinksListProps) {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<VStack
|
||||
rounded='5px'
|
||||
divider={<StackDivider borderColor='gray.200' />}
|
||||
spacing={0}
|
||||
align='stretch'
|
||||
>
|
||||
{children}
|
||||
</VStack>
|
||||
);
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Mark = styled.span`
|
||||
background: #1b1e21;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 4px;
|
||||
padding: 0 6px 0 7px;
|
||||
`;
|
@@ -1,10 +0,0 @@
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
|
||||
const MdRenderer = (props) => (
|
||||
<MDXProvider components={ MdxComponents }>
|
||||
{ props.children }
|
||||
</MDXProvider>
|
||||
);
|
||||
|
||||
export default MdRenderer;
|
20
components/md-renderer/index.tsx
Normal file
20
components/md-renderer/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
import { roadmapTheme } from '../../styles/theme';
|
||||
|
||||
type MdRendererType = {
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export default function MdRenderer(props: MdRendererType) {
|
||||
return (
|
||||
<ChakraProvider theme={roadmapTheme} resetCSS>
|
||||
<MDXProvider components={MdxComponents}>
|
||||
{props.children}
|
||||
</MDXProvider>
|
||||
</ChakraProvider>
|
||||
);
|
||||
};
|
@@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const EnrichedLink = props => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={ props.href } target={ isExternalUrl ? '_blank' : '_self' }>
|
||||
{ props.children }
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
26
components/md-renderer/mdx-components/a.tsx
Normal file
26
components/md-renderer/mdx-components/a.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type EnrichedLinkProps = {
|
||||
href: string;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Link = styled.a`
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
`;
|
||||
|
||||
const EnrichedLink = (props: EnrichedLinkProps) => {
|
||||
// Is external URL or is a media URL
|
||||
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(props.href);
|
||||
|
||||
return (
|
||||
<Link href={props.href} target={isExternalUrl ? '_blank' : '_self'}>
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrichedLink;
|
||||
|
21
components/md-renderer/mdx-components/badge-link.tsx
Normal file
21
components/md-renderer/mdx-components/badge-link.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Link, Text, Badge } from '@chakra-ui/react';
|
||||
|
||||
type BadgeLinkType = {
|
||||
target: string;
|
||||
badgeText: string;
|
||||
href: string;
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export function BadgeLink(props: BadgeLinkType) {
|
||||
const { target = '_blank', badgeText, href, children } = props;
|
||||
|
||||
return (
|
||||
<Text mb={0}>
|
||||
<Link fontWeight={500} textDecoration='underline' href={href} target={target}>
|
||||
<Badge colorScheme={'purple'} style={{ position: 'relative', top: '-2px' }}>{badgeText}</Badge> {children}
|
||||
</Link>
|
||||
</Text>
|
||||
);
|
||||
}
|
@@ -5,20 +5,21 @@ const BlockQuote = styled.blockquote`
|
||||
position: relative;
|
||||
background: #e8e8e8;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
p + h4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
|
||||
|
||||
& + p {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
10
components/md-renderer/mdx-components/code.tsx
Normal file
10
components/md-renderer/mdx-components/code.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Code as ChakraCode } from '@chakra-ui/react';
|
||||
|
||||
type CodeType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Code(props: CodeType) {
|
||||
return <ChakraCode bg='blue.500'>{props.children}</ChakraCode>;
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Link from 'components/icons/link.svg';
|
||||
import LinkIcon from 'components/icons/link.svg';
|
||||
|
||||
const linkify = (Component) => {
|
||||
return (props) => {
|
||||
const linkify = (Component: React.FunctionComponent<any>) => {
|
||||
return function EnrichedHeading(props: { children: string }): React.ReactNode {
|
||||
const text = props.children;
|
||||
const id = text.toLowerCase && text
|
||||
.toLowerCase()
|
||||
@@ -11,11 +12,11 @@ const linkify = (Component) => {
|
||||
.replace(/[?!]/g, '');
|
||||
|
||||
return (
|
||||
<Component id={ id }>
|
||||
<HeaderLink href={ `#${id}` }>
|
||||
<Link />
|
||||
<Component id={id}>
|
||||
<HeaderLink href={`#${id}`}>
|
||||
<LinkIcon />
|
||||
</HeaderLink>
|
||||
{ props.children }
|
||||
{props.children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -35,9 +36,10 @@ const HeaderLink = styled.a`
|
||||
const H1 = styled.h1`
|
||||
position: relative;
|
||||
font-size: 42px;
|
||||
line-height: 40px;
|
||||
font-weight: 700;
|
||||
margin: 32px 0 10px !important;
|
||||
|
||||
|
||||
&:hover ${HeaderLink} {
|
||||
display: flex;
|
||||
}
|
||||
@@ -67,11 +69,13 @@ const H6 = styled(H1).attrs({ as: 'h6' })`
|
||||
font-size: 18px;
|
||||
`;
|
||||
|
||||
export const Headings = {
|
||||
const Headings = {
|
||||
h1: linkify(H1),
|
||||
h2: linkify(H2),
|
||||
h3: linkify(H3),
|
||||
h4: linkify(H4),
|
||||
h5: linkify(H5),
|
||||
h6: linkify(H6),
|
||||
h6: linkify(H6)
|
||||
};
|
||||
|
||||
export default Headings;
|
@@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const IFrame = styled.iframe`
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
margin: 30px auto;
|
||||
`;
|
47
components/md-renderer/mdx-components/iframe.tsx
Normal file
47
components/md-renderer/mdx-components/iframe.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
type IFrameProps = {
|
||||
title: string;
|
||||
src: string;
|
||||
};
|
||||
|
||||
const AspectRatioBox = styled.div`
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:before {
|
||||
height: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
& > iframe {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function IFrame(props: IFrameProps) {
|
||||
return (
|
||||
<AspectRatioBox>
|
||||
<iframe
|
||||
frameBorder={0}
|
||||
title={props.title}
|
||||
src={props.src}
|
||||
allow={'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'}
|
||||
allowFullScreen
|
||||
/>
|
||||
</AspectRatioBox>
|
||||
);
|
||||
}
|
@@ -1,12 +1,15 @@
|
||||
import P from './p';
|
||||
import { Headings } from './heading';
|
||||
import { Code } from '@chakra-ui/react';
|
||||
import { P } from './p';
|
||||
import Headings from './heading';
|
||||
import { Pre } from './pre';
|
||||
import BlockQuote from './blockquote';
|
||||
import { Table } from './table';
|
||||
import { IFrame } from './iframe';
|
||||
import IFrame from './iframe';
|
||||
import { Img } from './img';
|
||||
import EnrichedLink from './a';
|
||||
import { BadgeLink } from '../../link/badge-link';
|
||||
import { BadgeLink } from './badge-link';
|
||||
import { Li, Ul } from './ul';
|
||||
import PremiumBlock from './premium-block';
|
||||
|
||||
const MdxComponents = {
|
||||
p: P,
|
||||
@@ -17,7 +20,11 @@ const MdxComponents = {
|
||||
table: Table,
|
||||
iframe: IFrame,
|
||||
img: Img,
|
||||
BadgeLink: BadgeLink
|
||||
code: Code,
|
||||
BadgeLink: BadgeLink,
|
||||
PremiumBlock: PremiumBlock,
|
||||
ul: Ul,
|
||||
li: Li
|
||||
};
|
||||
|
||||
export default MdxComponents;
|
@@ -1,20 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const P = styled.p`
|
||||
color: inherit;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 26px;
|
||||
margin: 0 0 12px;
|
||||
|
||||
img + em {
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
margin: 5px 0 10px;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export default P;
|
14
components/md-renderer/mdx-components/p.tsx
Normal file
14
components/md-renderer/mdx-components/p.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type EnrichedTextType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const P = styled.p`
|
||||
line-height: 27px;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
margin-bottom: 18px;
|
||||
`;
|
19
components/md-renderer/mdx-components/premium-block.tsx
Normal file
19
components/md-renderer/mdx-components/premium-block.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Heading, Text } from '@chakra-ui/react';
|
||||
import { LockIcon } from '@chakra-ui/icons';
|
||||
|
||||
type PremiumBlockProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function PremiumBlock(props: PremiumBlockProps) {
|
||||
return (
|
||||
<Box p='40px' textAlign='center' rounded='5px' mb='18px' bg='gray.50' borderWidth={1}>
|
||||
<LockIcon color='gray.300' height='45px' w='45px' mb='18px' />
|
||||
<Heading as='h3' fontSize='30px' mb='10px'>{props.title}</Heading>
|
||||
<Text mb='18px'>{props.description}</Text>
|
||||
<Button colorScheme='green'>Become a Member</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -5,7 +5,7 @@ export const Table = styled.table`
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
margin: 20px 0;
|
||||
|
||||
|
||||
th {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
@@ -16,10 +16,10 @@ export const Table = styled.table`
|
||||
vertical-align: middle;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
|
||||
td {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #EAEAEA;
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
12
components/md-renderer/mdx-components/ul.tsx
Normal file
12
components/md-renderer/mdx-components/ul.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { UnorderedList } from '@chakra-ui/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Ul = styled.ul`
|
||||
margin-left: 40px;
|
||||
margin-bottom: 18px;
|
||||
`;
|
||||
|
||||
export const Li = styled.li`
|
||||
margin-bottom: 7px;
|
||||
`;
|
46
components/opensource-banner.tsx
Normal file
46
components/opensource-banner.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Container, Heading, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
export function OpensourceBanner() {
|
||||
return (
|
||||
<Box borderTopWidth={1} pt={['45px', '45px', '70px']} pb={['20px', '20px', '30px']} textAlign='center'>
|
||||
<Container maxW='container.md'>
|
||||
<Heading fontSize={['25px', '25px', '35px']} mb={['10px', '10px', '20px']}>Open Source</Heading>
|
||||
<Text lineHeight='26px' fontSize={['15px', '15px', '16px']} mb='20px'>The project is OpenSource,
|
||||
<Link
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
href='https://github.com/search?o=desc&q=stars%3A%3E100000&s=stars&type=Repositories'
|
||||
target='_blank'
|
||||
borderBottomWidth={1}
|
||||
fontWeight={600}
|
||||
>7th most starred project on GitHub</Link> and is visited by hundreds of thousands of
|
||||
developers every month.</Text>
|
||||
<iframe
|
||||
src='https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large'
|
||||
frameBorder='0'
|
||||
scrolling='0'
|
||||
width='170'
|
||||
height='30'
|
||||
style={{ margin: 'auto', marginBottom: '30px' }}
|
||||
title='GitHub'
|
||||
/>
|
||||
|
||||
<Text lineHeight={['25px', '25px', '26px']} fontSize={['15px', '15px', '16px']} mb='15px'>A considerable amount of my time is spent doing unpaid
|
||||
community work on things that I hope will help humanity in some way. Your sponsorship helps me continue to
|
||||
produce more open-source and free educational material consumed by hundreds of thousands of developers every
|
||||
month.</Text>
|
||||
|
||||
<Box>
|
||||
<iframe
|
||||
src='https://ghbtns.com/github-btn.html?user=kamranahmedse&type=sponsor&size=large'
|
||||
frameBorder='0'
|
||||
scrolling='0'
|
||||
width='260'
|
||||
height='30'
|
||||
title='GitHub'
|
||||
style={{ margin: 'auto' }}
|
||||
/>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
export const CustomAd = () => {
|
||||
return (
|
||||
<div id="carbonads">
|
||||
<span>
|
||||
<span className="carbon-wrap">
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-img"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
<img
|
||||
src="/fm-img.png"
|
||||
alt="FM Logo"
|
||||
border="0"
|
||||
height="100"
|
||||
width="130"
|
||||
style={{ maxWidth: "130px" }}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://freemote.com/strategy?sl=roadmap"
|
||||
className="carbon-text"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
He Went from ZERO TO $74,000 as a Full Time Developer in 7 Weeks
|
||||
</a>
|
||||
</span>
|
||||
<a
|
||||
href="https://github.com/sponsors/kamranahmedse"
|
||||
className="carbon-poweredby"
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
>
|
||||
Sponsored by
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -1,71 +0,0 @@
|
||||
import siteConfig from 'content/site';
|
||||
import { FooterWrap } from './style.js';
|
||||
import './carbon.scss';
|
||||
import { CustomAd } from "./custom-ad";
|
||||
|
||||
const PageFooter = () => (
|
||||
<FooterWrap className="border-top">
|
||||
<div className="container">
|
||||
<div className="foot-cols-wrap row">
|
||||
<div className="site-meta-wrap col-12 col-sm-12 col-lg col-xl col-md-12">
|
||||
<div className="site-meta">
|
||||
<div className="brand-detail">
|
||||
<a href="/" className='brand'><img src="/brand.png" alt="" /> roadmap.sh</a>
|
||||
<span className="preposition">by</span>
|
||||
<a href="https://twitter.com/kamranahmedse" target="_blank" className='follow-author'>@kamranahmedse</a>
|
||||
</div>
|
||||
<div className="brand-explanation">
|
||||
<p>Community created roadmaps, articles, resources and journeys to help you choose your path and grow in your career.</p>
|
||||
</div>
|
||||
<p className='meta-links'>
|
||||
© roadmap.sh ·
|
||||
<a href="/about">FAQ</a> ·
|
||||
<a href="/terms">Terms</a> ·
|
||||
<a href="/privacy">Privacy</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="site-contribute foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className='foot-header'>Contribute</li>
|
||||
<li><a href='https://gum.co/roadmap-sh' target="_blank">Sponsor us</a></li>
|
||||
<li><a href={ siteConfig.url.addGuide } target="_blank">Write a Guide</a></li>
|
||||
<li><a href={ siteConfig.url.addRoadmap } target="_blank">Submit a Roadmap</a></li>
|
||||
<li><a href='/about'>About this Site</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Learn</li>
|
||||
<li><a href="/guides">Read Guides</a></li>
|
||||
<li><a href="/watch">Watch Videos</a></li>
|
||||
<li><a href="/podcasts">Podcasts</a></li>
|
||||
<li><a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1" target="_blank">YouTube</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="site-learn foot-col col-12 col-sm-4 col-lg-2">
|
||||
<ul>
|
||||
<li className="foot-header">Most Visited</li>
|
||||
<li><a href="/frontend">Frontend Roadmap</a></li>
|
||||
<li><a href="/backend">Backend Roadmap</a></li>
|
||||
<li><a href="/devops">DevOps Roadmap</a></li>
|
||||
<li><a href="/roadmaps">Upcoming</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomAd />
|
||||
|
||||
{/* Do not show on local */}
|
||||
{ process.env.GA_SECRET && false && (
|
||||
<>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CE7DLK3Y&placement=roadmapsh" id="_carbonads_js"></script>
|
||||
{/*<div id="codefund"></div>*/}
|
||||
{/*<script src="https://app.codefund.io/properties/681/funder.js" async></script>*/}
|
||||
</>
|
||||
) }
|
||||
</FooterWrap>
|
||||
);
|
||||
|
||||
export default PageFooter;
|
@@ -1,94 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FooterWrap = styled.div`
|
||||
padding: 65px 0;
|
||||
|
||||
.site-meta {
|
||||
margin-bottom: 30px;
|
||||
width: 350px;
|
||||
|
||||
.brand-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.preposition {
|
||||
margin: 0 7px;
|
||||
}
|
||||
|
||||
.follow-author {
|
||||
background-color: #1e99e6;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: #43aaea;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-explanation {
|
||||
color: #999;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-links {
|
||||
color: #a3a3a3;
|
||||
font-size: 15px;
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.foot-col {
|
||||
margin-bottom: 20px;
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.foot-header {
|
||||
font-weight: 500;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 2px;
|
||||
font-size: 15px;
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
35
components/page-header.tsx
Normal file
35
components/page-header.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Box, Container, Heading, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
type PageHeaderProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function PageHeader(props: PageHeaderProps) {
|
||||
const { title, subtitle, children } = props;
|
||||
|
||||
return (
|
||||
<Box pt={['25px', '20px', '45px']} pb={['20px', '15px', '30px']} borderBottomWidth={1} mb='30px'>
|
||||
<Container maxW='container.md' position='relative'>
|
||||
<Heading
|
||||
as='h1'
|
||||
color='black'
|
||||
fontSize={['28px', '33px', '40px']}
|
||||
fontWeight={700}
|
||||
mb={['2px', '2px', '5px']}
|
||||
>
|
||||
{title}
|
||||
</Heading>
|
||||
<Text fontSize={['13px', '14px', '15px']}>{subtitle}</Text>
|
||||
</Container>
|
||||
|
||||
{children && (
|
||||
<Container maxW='container.md'>
|
||||
{children}
|
||||
</Container>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import { HeaderWrap, Subtitle, Title } from './style';
|
||||
|
||||
const PageHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}) => (
|
||||
<HeaderWrap>
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageHeader;
|
@@ -1,22 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
@@ -1,13 +0,0 @@
|
||||
import { HeaderWrap, Subtitle, Title, Logo } from './style';
|
||||
|
||||
const PageLogoHeader = ({ title, subtitle, children, }) => (
|
||||
<HeaderWrap>
|
||||
<Logo src='/brand.png' alt='' />
|
||||
<Title>{ title }</Title>
|
||||
<Subtitle dangerouslySetInnerHTML={{ __html: subtitle }} />
|
||||
|
||||
{ children }
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default PageLogoHeader;
|
@@ -1,30 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
margin-bottom: 26px;
|
||||
`;
|
@@ -1,62 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faArrowLeft, faClock, faEnvelope, faHandshake } from '@fortawesome/free-solid-svg-icons';
|
||||
import { BadgeLink, BadgesList, DarkBadge, PrimaryBadge, SecondaryBadge } from 'components/badges';
|
||||
import siteConfig from "content/site";
|
||||
import { Description, Header, Title, MenuItemLink, MenuItems } from './style';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const RoadmapHeader = ({ roadmap, page = 'landscape' }) => (
|
||||
<Header>
|
||||
<Title>{ roadmap.title }</Title>
|
||||
<Description>{ roadmap.description }</Description>
|
||||
<BadgesList className="mt-4">
|
||||
<BadgeLink href="/roadmaps">
|
||||
<DarkBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faArrowLeft } />
|
||||
Other Roadmaps
|
||||
</DarkBadge>
|
||||
</BadgeLink>
|
||||
{ roadmap.upcoming && (
|
||||
<BadgeLink href="/signup">
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faClock } />
|
||||
Upcoming Roadmap
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
{ !roadmap.upcoming && (
|
||||
<BadgeLink href={ `${siteConfig.url.issue}?title=[${roadmap.title}] - Title Here` } target="_blank" className='d-none d-md-block' >
|
||||
<SecondaryBadge>
|
||||
<FontAwesomeIcon icon={ faHandshake } />
|
||||
Suggest Changes
|
||||
</SecondaryBadge>
|
||||
</BadgeLink>
|
||||
) }
|
||||
|
||||
<BadgeLink href="/signup">
|
||||
<PrimaryBadge>
|
||||
<FontAwesomeIcon className='d-none d-md-block' icon={ faEnvelope } />
|
||||
Send me Updates
|
||||
</PrimaryBadge>
|
||||
</BadgeLink>
|
||||
</BadgesList>
|
||||
|
||||
<MenuItems className="border-bottom">
|
||||
<div className={ classNames({ 'd-none': roadmap.title.toLowerCase() !== 'frontend developer' })}>
|
||||
<Link href={ `${roadmap.url}` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'landscape', }) }>Landscape</MenuItemLink>
|
||||
</Link>
|
||||
<Link href={ `${roadmap.url}/resources` } passHref>
|
||||
<MenuItemLink className={ classNames({ active: page === 'resources', }) }>Resources</MenuItemLink>
|
||||
</Link>
|
||||
{/*<Link href={ `${roadmap.url}/resources` } passHref>*/}
|
||||
{/* <MenuItemLink className={ classNames({ active: false, }) }>Project Ideas</MenuItemLink>*/}
|
||||
{/*</Link>*/}
|
||||
</div>
|
||||
</MenuItems>
|
||||
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default RoadmapHeader;
|
@@ -1,50 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Header = styled.div`
|
||||
text-align: center;
|
||||
padding: 45px 30px 10px;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
`;
|
||||
|
||||
export const Description = styled.p`
|
||||
font-size: 16px;
|
||||
color: #444444;
|
||||
`;
|
||||
|
||||
export const MenuItems = styled.div`
|
||||
margin: 35px 0 15px;
|
||||
`;
|
||||
|
||||
export const MenuItemLink = styled.a`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 10px 8px;
|
||||
text-decoration: none;
|
||||
color: rgb(102, 102, 102);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
&.active, &.active:hover {
|
||||
color: #2d2d2d;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
height: 0;
|
||||
left: 9px;
|
||||
right: 9px;
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: #111111;
|
||||
}
|
||||
`;
|
@@ -1,35 +0,0 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapResources = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.resources.replace(/^\//, '');
|
||||
const ResourcesContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } page='resources' />
|
||||
<Summary className="container">
|
||||
<MdRenderer>
|
||||
<ResourcesContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapResources;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
@@ -1,36 +0,0 @@
|
||||
import { Summary, SummaryContainer, UpcomingContainer } from './style';
|
||||
import classNames from 'classnames';
|
||||
import GuideBody from 'components/guide-body';
|
||||
import RoadmapHeader from 'components/roadmap-header';
|
||||
import SharePage from 'components/share-page';
|
||||
import MdRenderer from 'components/md-renderer';
|
||||
|
||||
const RoadmapSummary = ({ roadmap }) => {
|
||||
if (roadmap.upcoming) {
|
||||
return (
|
||||
<>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<UpcomingContainer>
|
||||
<GuideBody guide={{ fileName: 'upcoming' }} />
|
||||
</UpcomingContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = roadmap.path.replace(/^\//, '');
|
||||
const RoadmapContent = require(`../../content/${filePath}`).default;
|
||||
|
||||
return (
|
||||
<SummaryContainer>
|
||||
<RoadmapHeader roadmap={ roadmap } />
|
||||
<Summary className={classNames("container", { "container-small": roadmap.isTextHeavy })}>
|
||||
<MdRenderer>
|
||||
<RoadmapContent />
|
||||
</MdRenderer>
|
||||
<SharePage title={ roadmap.description } url={ roadmap.url } />
|
||||
</Summary>
|
||||
</SummaryContainer>
|
||||
)
|
||||
};
|
||||
|
||||
export default RoadmapSummary;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SummaryContainer = styled.div``;
|
||||
|
||||
export const UpcomingContainer = styled.div`
|
||||
text-align: center;
|
||||
padding: 40px 0 50px;
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Summary = styled.div`
|
||||
margin-top: 35px;
|
||||
min-height: 400px;
|
||||
max-width: 1000px;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
`;
|
47
components/roadmap/home-roadmap-item.tsx
Normal file
47
components/roadmap/home-roadmap-item.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Box, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { InfoIcon } from '@chakra-ui/icons';
|
||||
|
||||
type RoadmapGridItemProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
isCommunity?: boolean;
|
||||
colorIndex?: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'blue.900',
|
||||
'red.800',
|
||||
'green.800',
|
||||
'teal.800',
|
||||
'gray.800',
|
||||
'red.900'
|
||||
];
|
||||
|
||||
export function HomeRoadmapItem(props: RoadmapGridItemProps) {
|
||||
const { title, subtitle, isCommunity, colorIndex = 0, url } = props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
as={Link}
|
||||
href={url}
|
||||
_hover={{ textDecoration: 'none', transform: 'scale(1.02)' }}
|
||||
flex={1}
|
||||
shadow='2xl'
|
||||
bg={bgColorList[colorIndex] ?? bgColorList[0]}
|
||||
color='white'
|
||||
p='15px'
|
||||
rounded='10px'
|
||||
pos='relative'
|
||||
>
|
||||
{isCommunity && (
|
||||
<Tooltip label={'Community contribution'} hasArrow placement='top'>
|
||||
<InfoIcon opacity={0.5} position='absolute' top='10px' right='10px' />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Heading fontSize={['17px', '17px', '22px']} mb='5px'>{title}</Heading>
|
||||
<Text color='gray.200' fontSize={['13px']}>{subtitle}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
89
components/roadmap/roadmap-grid-item.tsx
Normal file
89
components/roadmap/roadmap-grid-item.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Badge, Box, Flex, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { InfoIcon } from '@chakra-ui/icons';
|
||||
|
||||
type RoadmapGridItemProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
href: string;
|
||||
isCommunity?: boolean;
|
||||
isUpcoming?: boolean;
|
||||
colorIndex?: number;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'gray.900',
|
||||
'purple.900',
|
||||
'blue.900',
|
||||
'red.900',
|
||||
'green.900',
|
||||
'teal.900',
|
||||
'yellow.900',
|
||||
'cyan.900',
|
||||
'pink.900',
|
||||
|
||||
'gray.800',
|
||||
'purple.800',
|
||||
'blue.800',
|
||||
'red.800',
|
||||
'green.800',
|
||||
'teal.800',
|
||||
'yellow.800',
|
||||
'cyan.800',
|
||||
'pink.800',
|
||||
|
||||
'gray.700',
|
||||
'purple.700',
|
||||
'blue.700',
|
||||
'red.700',
|
||||
'green.700',
|
||||
'teal.700',
|
||||
'yellow.700',
|
||||
'cyan.700',
|
||||
'pink.700',
|
||||
|
||||
'gray.600',
|
||||
'purple.600',
|
||||
'blue.600',
|
||||
'red.600',
|
||||
'green.600',
|
||||
'teal.600',
|
||||
'yellow.600',
|
||||
'cyan.600',
|
||||
'pink.600'
|
||||
];
|
||||
|
||||
export function RoadmapGridItem(props: RoadmapGridItemProps) {
|
||||
const { title, subtitle, isCommunity = false, isUpcoming = false, colorIndex = 0, href = '/' } = props;
|
||||
|
||||
return (
|
||||
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={href} shadow='xl' p='20px'
|
||||
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1} pos='relative'>
|
||||
|
||||
{isCommunity && (
|
||||
<Tooltip label={'Community contribution'} hasArrow placement='top'>
|
||||
<InfoIcon opacity={0.5} color='gray.100' position='absolute' top='10px' right='10px' />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
|
||||
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
|
||||
|
||||
{isUpcoming && (
|
||||
<Flex
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
pos='absolute'
|
||||
left={0}
|
||||
right={0}
|
||||
top={0}
|
||||
bottom={0}
|
||||
rounded='10px'
|
||||
>
|
||||
<Text color='white' bg='yellow.900' zIndex={1} fontWeight={600} p={'5px 10px'}
|
||||
rounded='10px'>Upcoming</Text>
|
||||
<Box bg={'black'} pos='absolute' top={0} left={0} right={0} bottom={0} rounded={'10px'} opacity={0.5} />
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import { Badge, BlockItem, ItemSubtitle, ItemTitle, ItemWrap } from './style';
|
||||
|
||||
const RowBlock = ({
|
||||
title,
|
||||
subtitle,
|
||||
url,
|
||||
badge,
|
||||
openExternal = false,
|
||||
disabled = false,
|
||||
children = null
|
||||
}) => (
|
||||
<ItemWrap className="col-md-6 col-lg-4 col-xl-4">
|
||||
<BlockItem href={ url } disabled={ disabled } target={openExternal ? '_blank' : '_self'}>
|
||||
{ !children && (
|
||||
<>
|
||||
<ItemTitle>
|
||||
{ title }
|
||||
{ badge && <Badge>{ badge }</Badge>}
|
||||
</ItemTitle>
|
||||
<ItemSubtitle>{ subtitle }</ItemSubtitle>
|
||||
</>
|
||||
) }
|
||||
{ children }
|
||||
</BlockItem>
|
||||
</ItemWrap>
|
||||
);
|
||||
|
||||
export default RowBlock;
|
@@ -1,62 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ItemWrap = styled.div`
|
||||
padding: 0 10px 20px;
|
||||
`;
|
||||
|
||||
export const BlockItem = styled.a`
|
||||
min-height: 114px;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 5px 10px;
|
||||
transition: box-shadow 0.2s ease 0s;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 1px;
|
||||
max-width: 100%;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
text-decoration:none;
|
||||
opacity: ${ props => props.disabled ? 0.5 : 1 };
|
||||
|
||||
&:hover {
|
||||
text-decoration:none;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0 30px 60px;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ItemTitle = styled.h3`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
color: #101010;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
export const Badge = styled.span`
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
background: #8a8a8a;
|
||||
color: white;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10px;
|
||||
height: 18px;
|
||||
`;
|
||||
|
||||
export const ItemSubtitle = styled.p`
|
||||
font-size: 14px;
|
||||
color: rgb(102, 102, 102);
|
||||
white-space: normal;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
`;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIcon = styled.a`
|
||||
display: block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
color: #757575;
|
||||
transition: all 0.2s;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
color: #000000
|
||||
}
|
||||
`;
|
@@ -1,37 +0,0 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faFacebookSquare, faHackerNewsSquare, faRedditSquare, faTwitterSquare } from '@fortawesome/free-brands-svg-icons';
|
||||
|
||||
import { getFacebookShareUrl, getHnShareUrl, getRedditShareUrl, getTwitterShareUrl } from 'lib/url';
|
||||
import { ShareIconsList, ShareWrap } from './style';
|
||||
import { ShareIcon } from 'components/share-icon';
|
||||
|
||||
const SharePage = ({
|
||||
url,
|
||||
title,
|
||||
twitterUsername,
|
||||
}) => (
|
||||
<ShareWrap>
|
||||
<ShareIconsList className="d-none d-sm-flex">
|
||||
<ShareIcon
|
||||
href={ getTwitterShareUrl({
|
||||
text: `${title} ${twitterUsername ? `by @${twitterUsername}` : ''}`,
|
||||
url: url
|
||||
})}
|
||||
target="_blank"
|
||||
>
|
||||
<FontAwesomeIcon icon={ faTwitterSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getFacebookShareUrl({ text: title, url: url }) } target="_blank">
|
||||
<FontAwesomeIcon icon={ faFacebookSquare } />
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getHnShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={faHackerNewsSquare}/>
|
||||
</ShareIcon>
|
||||
<ShareIcon href={ getRedditShareUrl({ text: title, url: url })} target="_blank">
|
||||
<FontAwesomeIcon icon={ faRedditSquare } />
|
||||
</ShareIcon>
|
||||
</ShareIconsList>
|
||||
</ShareWrap>
|
||||
);
|
||||
|
||||
export default SharePage;
|
@@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ShareIconsList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: sticky;
|
||||
top: 65px;
|
||||
`;
|
||||
|
||||
export const ShareWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
padding: 0 0;
|
||||
top: 0;
|
||||
left: -40px;
|
||||
min-height: 100%;
|
||||
`;
|
@@ -1,17 +0,0 @@
|
||||
import { SignUpWrap, Title, Subtitle, Textbox, Button } from './style';
|
||||
|
||||
const SignUpForm = () => (
|
||||
<SignUpWrap>
|
||||
<Title>Subscribe</Title>
|
||||
<Subtitle>Enter your email below to get notified about the new roadmaps, guides and updates</Subtitle>
|
||||
<form action="https://kamranahmed.us9.list-manage.com/subscribe/post?u=6f57b741a6a939744f1f203a0&id=f9ca4d6aee" target="_blank" method="post">
|
||||
<Textbox type="email" name="EMAIL" required placeholder="Your email" />
|
||||
<div style={{position: 'absolute', left: '-5000px'}}>
|
||||
<input type="text" name="b_6f57b741a6a939744f1f203a0_f9ca4d6aee" tabIndex="-1" />
|
||||
</div>
|
||||
<Button type="submit">Subscribe</Button>
|
||||
</form>
|
||||
</SignUpWrap>
|
||||
);
|
||||
|
||||
export default SignUpForm;
|
@@ -1,57 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SignUpWrap = styled.div`
|
||||
max-width: 400px;
|
||||
margin: 200px auto;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export const Title = styled.h1`
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 17px;
|
||||
`;
|
||||
|
||||
export const Subtitle = styled.p`
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
export const Textbox = styled.input`
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
height: auto;
|
||||
outline: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 5px;
|
||||
|
||||
&:focus {
|
||||
border: 1px solid #101010;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Button = styled.button`
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
margin-top: 10px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
background: #000000;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
@@ -1,26 +0,0 @@
|
||||
import { HeaderWrap } from './style';
|
||||
|
||||
const SiteNav = () => (
|
||||
<HeaderWrap>
|
||||
<div className='top-row container'>
|
||||
<div className='flex-grow-1 brand'>
|
||||
<a href='/'>
|
||||
<img src='/brand.png' alt='' />
|
||||
</a>
|
||||
</div>
|
||||
<div className='nav-links'>
|
||||
<a href='/guides'>Read</a>
|
||||
<a href='/watch'>
|
||||
Watch
|
||||
<span className='new-item' />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='ml-3 align-items-center d-none d-md-flex'>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=kamranahmedse&repo=developer-roadmap&type=star&count=true&size=large" frameBorder="0" scrolling="0" width="190px" height="30px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</HeaderWrap>
|
||||
);
|
||||
|
||||
export default SiteNav;
|
@@ -1,61 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrap = styled.div`
|
||||
padding: 15px 0;
|
||||
font-size: 18px;
|
||||
|
||||
.top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.github-button {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand img {
|
||||
padding: 5px 0;
|
||||
height: 50px;
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
a {
|
||||
position: relative;
|
||||
padding: 0 10px;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.signup {
|
||||
background: #101010;
|
||||
border-radius: 5px;
|
||||
color: #ffffff;
|
||||
padding: 7px 10px;
|
||||
margin-left: 15px;
|
||||
|
||||
&:hover {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
}
|
||||
|
||||
.new-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
background: #e25712;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import { BannerWrap, CloseSponsor, SponsorLogo } from './style';
|
||||
|
||||
export function SponsorBanner({ onCloseBanner = () => null }) {
|
||||
return (
|
||||
<div className='row'>
|
||||
<div className='col p-0'>
|
||||
<BannerWrap
|
||||
href={`https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1`}
|
||||
target='_blank'
|
||||
className='alert alert-info'
|
||||
>
|
||||
<SponsorLogo src='/sponsors/youtube.svg' />
|
||||
We now have a youtube channel. <span className='d-none d-sm-inline-block'>Subscribe for the video content.</span>
|
||||
|
||||
<CloseSponsor
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onCloseBanner();
|
||||
}}
|
||||
className='close'>×</CloseSponsor>
|
||||
</BannerWrap>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const BannerWrap = styled.a`
|
||||
margin-bottom: 0;
|
||||
//background: #101010;
|
||||
//color: white;
|
||||
background: #ffe0b2;
|
||||
color: #d8362a;
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 10px 15px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
transition: all 200ms;
|
||||
|
||||
&:hover {
|
||||
background: #ffd698;
|
||||
color: #bd2015;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SponsorLogo = styled.img`
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
`;
|
||||
|
||||
export const EmojiWrap = styled.img`
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin: 0 6px 0 6px;
|
||||
`;
|
||||
|
||||
export const CloseSponsor = styled.span`
|
||||
text-shadow: none;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
color: #101010;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: #101010;
|
||||
}
|
||||
`;
|
31
components/sticky-banner.tsx
Normal file
31
components/sticky-banner.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Flex, Link, Text } from '@chakra-ui/react';
|
||||
import YouTubeLogo from '../components/icons/youtube.svg';
|
||||
import siteConfig from '../content/site.json';
|
||||
|
||||
export function StickyBanner() {
|
||||
return (
|
||||
<Flex as={Link}
|
||||
href={siteConfig.url.youtube}
|
||||
bg={'yellow.300'}
|
||||
color='gray.900'
|
||||
// bg={'teal.900'}
|
||||
// color='gray.300'
|
||||
alignItems='center'
|
||||
position='fixed'
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={999}
|
||||
justifyContent='center'
|
||||
py='8px'
|
||||
_hover={{ textDecoration: 'none', bg: 'yellow.400' }}
|
||||
// _hover={{ textDecoration: 'none', bg: 'teal.800', color: 'gray.100' }}
|
||||
target='_blank'
|
||||
>
|
||||
<YouTubeLogo style={{ height: '20px', display: 'inline-block', marginRight: '7px' }} />
|
||||
<Text as='span' fontWeight={500} fontSize='14px'>
|
||||
<Text as='span'>We now have a YouTube Channel. <Text as='span' d={['none', 'inline']}>Subscribe for the video
|
||||
content.</Text></Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TosPage = styled.div`
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
}
|
||||
`;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user