Compare commits

..

9 Commits

Author SHA1 Message Date
Kamran Ahmed
6f343d3eb5 Add vite, vitest and playwright 2022-10-13 17:55:46 +04:00
Aroyan Bakti Setiawan
ae02653079 feat: add unfetch content 2022-10-13 20:49:02 +08:00
Aroyan
61244b6cb5 Merge branch 'kamranahmedse:master' into master 2022-10-13 20:44:51 +08:00
Aroyan
0ce47a3681 Merge branch 'kamranahmedse:master' into master 2022-10-13 20:17:18 +08:00
Aroyan
c2574c3787 Merge branch 'kamranahmedse:master' into master 2022-10-05 11:29:04 +08:00
Aroyan
4b57977cc9 Merge branch 'kamranahmedse:master' into master 2022-09-30 21:33:57 +08:00
Aroyan
e58a516572 Merge branch 'kamranahmedse:master' into master 2022-09-25 15:44:52 +08:00
Aroyan
a37fea9d5b Merge branch 'kamranahmedse:master' into master 2022-09-24 20:38:45 +08:00
Aroyan Bakti Setiawan
3daa7ef086 feat: add Set JavaScript content 2022-09-23 18:02:12 +08:00
3980 changed files with 209624 additions and 28755 deletions

18
.eslintrc Normal file
View 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"
]
}
}

BIN
.github/images/banner.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

1
.github/sponsors/doppler-logo.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 3473 1069"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#111;}.cls-3{fill-rule:evenodd;fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="658.73" y1="777.7" x2="341.45" y2="352.06" gradientTransform="matrix(1, 0, 0, -1, 0, 1070)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33a9ff"/><stop offset="1" stop-color="#1673ff"/></linearGradient></defs><rect class="cls-1" width="3473" height="1069"/><path class="cls-2" d="M1054.06,633.32q4.93.45,11.23.9H1081q52.55,0,77.7-26.5,25.59-26.49,25.59-73.18,0-48.94-24.25-74.09t-76.79-25.14q-7.18,0-14.82.45-5.78,0-11,.51a3.73,3.73,0,0,0-3.33,3.74Zm202.54-98.78Q1256.6,575,1244,605t-35.92,49.84q-22.9,19.75-56.14,29.63t-74.55,9.88q-18.86,0-44-1.79A338.32,338.32,0,0,1,984,686.3V386.41a3.8,3.8,0,0,1,3.13-3.75,386.34,386.34,0,0,1,47.17-5.27q26.49-1.8,45.36-1.8,40,0,72.3,9,32.79,9,56.14,28.29T1244,462.25Q1256.61,492.33,1256.6,534.54Z"/><path class="cls-2" d="M1397.52,534.54q0,22.89,5.39,41.31a103.13,103.13,0,0,0,16.17,31.87,74.66,74.66,0,0,0,26.05,20.21q15.27,7.19,35,7.18,19.3,0,34.58-7.18a69.47,69.47,0,0,0,26-20.21A91.41,91.41,0,0,0,1557,575.85q5.83-18.42,5.84-41.31T1557,493.23q-5.39-18.86-16.17-31.88a67.73,67.73,0,0,0-26-20.65q-15.27-7.18-34.58-7.19-19.77,0-35,7.64a72.5,72.5,0,0,0-26.05,20.65q-10.33,13-16.17,31.88A145.23,145.23,0,0,0,1397.52,534.54Zm237.57,0q0,40-12.12,70.49-11.68,30.09-32.34,50.74a134.89,134.89,0,0,1-49.4,30.53,176.88,176.88,0,0,1-61.07,10.33A174.23,174.23,0,0,1,1420,686.3a140,140,0,0,1-49.4-30.53q-21.11-20.65-33.23-50.74-12.13-30.53-12.13-70.49t12.58-70q12.57-30.53,33.68-51.18a142,142,0,0,1,49.4-31A171.39,171.39,0,0,1,1480.16,372a174.08,174.08,0,0,1,60.18,10.33,136.87,136.87,0,0,1,49.4,31q21.1,20.65,33.23,51.18Q1635.09,494.58,1635.09,534.54Z"/><path class="cls-2" d="M1810.48,375.59q69.62,0,106.88,24.7,37.28,24.24,37.28,79.92,0,56.13-37.72,81.27-37.73,24.69-107.79,24.69H1791a3.83,3.83,0,0,0-3.83,3.83v96.51a3.83,3.83,0,0,1-3.83,3.83h-66.23V386.83a3.81,3.81,0,0,1,3.1-3.75,400.76,400.76,0,0,1,45.4-5.69Q1791.18,375.59,1810.48,375.59Zm4.49,59.72q-7.63,0-15.27.45-5,.3-9.06.62a3.8,3.8,0,0,0-3.51,3.8v86.28h22q36.38,0,54.79-9.88t18.42-36.82q0-13-4.94-21.55a32.47,32.47,0,0,0-13.48-13.47q-8.54-5.39-21.1-7.19A159.75,159.75,0,0,0,1815,435.31Z"/><path class="cls-2" d="M2123.07,375.59q69.61,0,106.89,24.7,37.27,24.24,37.27,79.92,0,56.13-37.72,81.27-37.73,24.69-107.78,24.69h-18.18a3.83,3.83,0,0,0-3.83,3.83v96.51a3.83,3.83,0,0,1-3.83,3.83h-66.23V386.82a3.8,3.8,0,0,1,3.1-3.74,400.76,400.76,0,0,1,45.4-5.69Q2103.77,375.59,2123.07,375.59Zm4.49,59.72q-7.64,0-15.27.45-5,.3-9.06.62a3.81,3.81,0,0,0-3.51,3.8v86.28h22q36.38,0,54.78-9.88t18.42-36.82q0-13-4.94-21.55a32.47,32.47,0,0,0-13.48-13.47q-8.52-5.39-21.1-7.19A159.75,159.75,0,0,0,2127.56,435.31Z"/><path class="cls-2" d="M2540.5,630.17a1.29,1.29,0,0,1,1.28,1.28v57.62a1.28,1.28,0,0,1-1.28,1.27H2342.25V401.41a3.83,3.83,0,0,1,2.81-3.69l67.25-18.54v251Z"/><path class="cls-2" d="M2618.88,690.34V383a3.84,3.84,0,0,1,3.83-3.83h215a1.28,1.28,0,0,1,1.23,1.64l-16.44,56.26a1.26,1.26,0,0,1-1.22.92H2692.77a3.83,3.83,0,0,0-3.83,3.83v57.24H2803.1a1.27,1.27,0,0,1,1.23,1.63l-16,54.92a1.28,1.28,0,0,1-1.22.92h-94.34a3.82,3.82,0,0,0-3.83,3.83v71.15h154.72a1.28,1.28,0,0,1,1.23,1.63l-16.34,56.27a1.27,1.27,0,0,1-1.22.92Z"/><path class="cls-2" d="M3006,375.59q70.07,0,107.34,25.15,37.28,24.69,37.27,77.22,0,32.79-15.27,53.44-14.82,20.19-43.11,31.87,9.43,11.68,19.76,26.94,10.34,14.82,20.21,31.43,10.32,16.17,19.76,34.13,8.93,16.58,16.65,32.75a1.28,1.28,0,0,1-1.16,1.82H3091.6a1.27,1.27,0,0,1-1.11-.65q-8.37-15-17.15-30.33-8.53-15.72-18-30.53-9-14.82-18-27.84a286.92,286.92,0,0,0-18-24.25h-30.76a3.83,3.83,0,0,0-3.82,3.83V686.51a3.83,3.83,0,0,1-3.83,3.83h-66.23V386.82a3.8,3.8,0,0,1,3.09-3.74,400,400,0,0,1,44.06-5.69Q2986.67,375.59,3006,375.59Zm4.05,59.72q-7.64,0-13.93.45-4,.3-7.71.61a3.82,3.82,0,0,0-3.51,3.81v80.89h19.76q39.51,0,56.58-9.88t17.07-33.67q0-22.9-17.52-32.33Q3043.71,435.31,3010,435.31Z"/><path class="cls-3" d="M307.26,310a1.79,1.79,0,0,0-1.5,2.75l89.7,140.38a14.19,14.19,0,0,0,12,6.58H528.38c39.92,0,64.87,35.28,64.72,74.79s-26.74,74.44-64.72,74.44H404.77a4.71,4.71,0,0,0-4.71,4.75V754.25a4.72,4.72,0,0,0,4.72,4.75H560.62C689.12,759,753,637.1,753.09,534.5S690,310,560.62,310ZM367,609.29H336.16C318.4,609.29,304,626,304,646.71V757.66a1.28,1.28,0,0,0,1.28,1.28h30.88c17.76,0,32.15-16.75,32.15-37.41v-111A1.27,1.27,0,0,0,367,609.29Z"/></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

59
.github/sponsors/oss-logo.svg vendored Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 646.6 105.7" style="enable-background:new 0 0 646.6 105.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#104366;}
.st1{fill:#4086C6;}
</style>
<g>
<path class="st0" d="M21.1,79.8c-6.6-3.5-11.7-8.4-15.5-14.6C1.9,59,0,52,0,44.3c0-7.8,1.9-14.7,5.6-20.9
c3.7-6.2,8.9-11.1,15.5-14.6c6.6-3.5,14-5.3,22.2-5.3c8.2,0,15.6,1.8,22.1,5.3c6.6,3.5,11.7,8.4,15.5,14.6
c3.8,6.2,5.6,13.2,5.6,20.9c0,7.8-1.9,14.7-5.6,20.9c-3.8,6.2-8.9,11.1-15.5,14.6c-6.5,3.5-13.9,5.3-22.1,5.3
C35,85.1,27.6,83.4,21.1,79.8z M55.9,66.3c3.8-2.1,6.7-5.1,8.9-9c2.1-3.8,3.2-8.2,3.2-13.1c0-4.9-1.1-9.3-3.2-13.1
c-2.1-3.8-5.1-6.8-8.9-9c-3.8-2.1-8-3.2-12.6-3.2c-4.7,0-8.9,1.1-12.6,3.2s-6.7,5.1-8.9,9c-2.1,3.8-3.2,8.2-3.2,13.1
c0,4.9,1.1,9.3,3.2,13.1c2.1,3.8,5.1,6.8,8.9,9c3.8,2.1,8,3.2,12.6,3.2C47.9,69.5,52.1,68.5,55.9,66.3z"/>
<path class="st0" d="M108.1,82.6c-5.8-1.7-10.5-3.9-14.1-6.6l6.2-13.8c3.4,2.5,7.4,4.5,12.1,6c4.7,1.5,9.3,2.3,14,2.3
c5.2,0,9-0.8,11.5-2.3c2.5-1.5,3.7-3.6,3.7-6.2c0-1.9-0.7-3.4-2.2-4.7c-1.5-1.2-3.3-2.2-5.6-3c-2.3-0.8-5.4-1.6-9.3-2.5
c-6-1.4-11-2.9-14.8-4.3c-3.8-1.4-7.1-3.7-9.9-6.9c-2.7-3.2-4.1-7.4-4.1-12.6c0-4.6,1.2-8.7,3.7-12.5c2.5-3.7,6.2-6.7,11.2-8.9
c5-2.2,11.1-3.3,18.3-3.3c5,0,10,0.6,14.8,1.8c4.8,1.2,9,2.9,12.6,5.2l-5.6,13.9c-7.3-4.1-14.6-6.2-21.9-6.2
c-5.1,0-8.9,0.8-11.3,2.5c-2.4,1.7-3.7,3.8-3.7,6.5c0,2.7,1.4,4.7,4.2,6c2.8,1.3,7.1,2.6,12.9,3.9c6,1.4,11,2.9,14.8,4.3
c3.8,1.4,7.1,3.7,9.9,6.8c2.7,3.1,4.1,7.3,4.1,12.5c0,4.5-1.3,8.6-3.8,12.4c-2.5,3.7-6.3,6.7-11.3,8.9c-5,2.2-11.2,3.3-18.4,3.3
C120,85.1,113.9,84.3,108.1,82.6z"/>
<path class="st0" d="M180.1,82.6c-5.8-1.7-10.5-3.9-14.1-6.6l6.2-13.8c3.4,2.5,7.4,4.5,12.1,6c4.7,1.5,9.3,2.3,14,2.3
c5.2,0,9-0.8,11.5-2.3c2.5-1.5,3.7-3.6,3.7-6.2c0-1.9-0.7-3.4-2.2-4.7c-1.5-1.2-3.3-2.2-5.6-3c-2.3-0.8-5.4-1.6-9.3-2.5
c-6-1.4-10.9-2.9-14.8-4.3c-3.8-1.4-7.1-3.7-9.9-6.9c-2.7-3.2-4.1-7.4-4.1-12.6c0-4.6,1.2-8.7,3.7-12.5c2.5-3.7,6.2-6.7,11.2-8.9
s11.1-3.3,18.3-3.3c5,0,10,0.6,14.8,1.8c4.8,1.2,9,2.9,12.6,5.2l-5.6,13.9c-7.3-4.1-14.6-6.2-21.9-6.2c-5.1,0-8.9,0.8-11.3,2.5
c-2.4,1.7-3.7,3.8-3.7,6.5c0,2.7,1.4,4.7,4.2,6c2.8,1.3,7.1,2.6,12.9,3.9c6,1.4,10.9,2.9,14.8,4.3s7.1,3.7,9.9,6.8
c2.7,3.1,4.1,7.3,4.1,12.5c0,4.5-1.3,8.6-3.8,12.4c-2.5,3.7-6.3,6.7-11.3,8.9c-5,2.2-11.2,3.3-18.4,3.3
C192,85.1,186,84.3,180.1,82.6z"/>
<path class="st1" d="M293.2,79.1c-6.2-3.5-11.1-8.2-14.7-14.3c-3.6-6.1-5.4-12.9-5.4-20.5c0-7.6,1.8-14.5,5.4-20.5
c3.6-6.1,8.5-10.9,14.7-14.3c6.2-3.5,13.2-5.2,20.9-5.2c5.7,0,11,0.9,15.8,2.8c4.8,1.8,8.9,4.6,12.3,8.2l-3.6,3.7
c-6.3-6.2-14.4-9.4-24.3-9.4c-6.6,0-12.6,1.5-18.1,4.5c-5.4,3-9.7,7.2-12.8,12.5s-4.6,11.2-4.6,17.8s1.5,12.5,4.6,17.8
c3.1,5.3,7.3,9.5,12.8,12.5c5.4,3,11.4,4.5,18.1,4.5c9.8,0,17.9-3.2,24.3-9.5l3.6,3.7c-3.4,3.6-7.5,6.4-12.4,8.2
c-4.9,1.9-10.1,2.8-15.7,2.8C306.3,84.3,299.4,82.6,293.2,79.1z"/>
<path class="st1" d="M395.4,30c3.9,3.7,5.9,9.2,5.9,16.4v37.4h-5.4V73.3c-1.9,3.5-4.6,6.2-8.2,8.1c-3.6,1.9-7.9,2.9-13,2.9
c-6.5,0-11.7-1.5-15.5-4.6c-3.8-3.1-5.7-7.1-5.7-12.2c0-4.9,1.8-8.9,5.2-11.9c3.5-3,9.1-4.6,16.8-4.6h20.2v-4.7
c0-5.5-1.5-9.7-4.5-12.5c-3-2.9-7.3-4.3-13-4.3c-3.9,0-7.7,0.7-11.2,2c-3.6,1.4-6.6,3.2-9.1,5.4l-2.8-4.1c2.9-2.6,6.5-4.7,10.6-6.2
c4.1-1.5,8.5-2.2,13-2.2C385.9,24.4,391.5,26.3,395.4,30z M387.9,76.2c3.4-2.3,6-5.5,7.7-9.8V55.3h-20.1c-5.8,0-10,1.1-12.6,3.2
c-2.6,2.1-3.9,5-3.9,8.7c0,3.8,1.4,6.9,4.3,9.1c2.9,2.2,6.9,3.3,12.1,3.3C380.3,79.6,384.5,78.5,387.9,76.2z"/>
<path class="st1" d="M469,28.2c4.4,2.6,7.9,6.1,10.4,10.6c2.5,4.5,3.8,9.7,3.8,15.5c0,5.8-1.3,11-3.8,15.5
c-2.5,4.6-6,8.1-10.4,10.6c-4.4,2.5-9.4,3.8-14.9,3.8c-5.2,0-9.9-1.2-14.1-3.7c-4.2-2.4-7.5-5.9-9.8-10.2v35.3h-5.6V24.8h5.4v13.9
c2.3-4.5,5.6-8,9.9-10.6c4.2-2.5,9-3.8,14.3-3.8C459.6,24.4,464.6,25.7,469,28.2z M465.9,76c3.6-2.1,6.5-5,8.5-8.8
c2.1-3.8,3.1-8.1,3.1-12.9c0-4.8-1-9.1-3.1-12.9c-2.1-3.8-4.9-6.7-8.5-8.8c-3.6-2.1-7.7-3.2-12.2-3.2c-4.5,0-8.6,1.1-12.1,3.2
c-3.6,2.1-6.4,5-8.5,8.8c-2.1,3.8-3.1,8.1-3.1,12.9c0,4.8,1,9.1,3.1,12.9c2.1,3.8,4.9,6.7,8.5,8.8c3.6,2.1,7.6,3.2,12.1,3.2
C458.3,79.1,462.3,78.1,465.9,76z"/>
<path class="st1" d="M500.3,9.2c-0.9-0.9-1.4-1.9-1.4-3.2c0-1.3,0.5-2.4,1.4-3.3c0.9-0.9,2-1.4,3.3-1.4c1.3,0,2.4,0.4,3.3,1.3
c0.9,0.9,1.4,1.9,1.4,3.2c0,1.3-0.5,2.4-1.4,3.3c-0.9,0.9-2,1.4-3.3,1.4C502.3,10.5,501.2,10.1,500.3,9.2z M500.7,24.8h5.6v58.9
h-5.6V24.8z"/>
<path class="st1" d="M559.4,80c-1.4,1.4-3.2,2.4-5.4,3.1c-2.1,0.7-4.4,1.1-6.7,1.1c-5.1,0-9.1-1.4-11.9-4.2
c-2.8-2.8-4.2-6.8-4.2-11.8V29.7h-10.8v-4.9h10.8V12h5.6v12.9h18.7v4.9H537v37.9c0,3.8,0.9,6.8,2.8,8.8c1.8,2,4.6,3,8.2,3
c3.7,0,6.7-1.1,9.1-3.3L559.4,80z"/>
<path class="st1" d="M611.8,30c3.9,3.7,5.9,9.2,5.9,16.4v37.4h-5.4V73.3c-1.9,3.5-4.6,6.2-8.2,8.1c-3.6,1.9-7.9,2.9-13,2.9
c-6.5,0-11.7-1.5-15.5-4.6c-3.8-3.1-5.7-7.1-5.7-12.2c0-4.9,1.8-8.9,5.2-11.9c3.5-3,9.1-4.6,16.8-4.6H612v-4.7
c0-5.5-1.5-9.7-4.5-12.5c-3-2.9-7.3-4.3-13-4.3c-3.9,0-7.7,0.7-11.2,2c-3.6,1.4-6.6,3.2-9.1,5.4l-2.8-4.1c2.9-2.6,6.5-4.7,10.6-6.2
c4.1-1.5,8.5-2.2,13-2.2C602.3,24.4,607.9,26.3,611.8,30z M604.3,76.2c3.4-2.3,6-5.5,7.7-9.8V55.3h-20.1c-5.8,0-10,1.1-12.6,3.2
c-2.6,2.1-3.9,5-3.9,8.7c0,3.8,1.4,6.9,4.3,9.1c2.9,2.2,6.9,3.3,12.1,3.3C596.7,79.6,600.9,78.5,604.3,76.2z"/>
<path class="st1" d="M640.9,0h5.6v83.8h-5.6V0z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,11 @@
<svg width="1354" height="420" viewBox="0 0 1354 420" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1354" height="420" rx="20" fill="white"/>
<path d="M434.751 133.122H466.637L489.595 227.729C493.852 245.585 494.697 256.219 494.697 256.219H495.128C495.128 256.219 496.61 245.808 500.867 227.729L522.757 133.122H558.9L582.066 227.729C586.53 246.223 587.598 256.219 587.598 256.219H588.236C588.236 256.219 588.666 246.223 592.907 227.729L615.02 133.122H646.907L606.523 288.313H571.017L546.576 194.344C541.474 173.936 541.044 164.801 541.044 164.801H540.614C540.614 164.801 540.183 173.936 535.512 194.344L512.553 288.313H475.996L434.751 133.122Z" fill="black"/>
<path d="M641.583 231.934C641.583 196.428 664.541 173.47 699.202 173.47C733.639 173.47 756.597 196.428 756.597 231.934C756.597 267.647 733.639 290.828 699.202 290.828C664.557 290.812 641.583 267.647 641.583 231.934ZM726.832 231.934C726.832 208.976 715.783 195.998 699.202 195.998C681.346 195.998 671.349 210.458 671.349 231.934C671.349 255.323 682.398 268.284 699.202 268.284C717.058 268.284 726.832 253.824 726.832 231.934Z" fill="black"/>
<path d="M770.836 175.21H799.103V196.048H799.741C804.635 185.207 816.322 174.365 836.299 174.365C839.695 174.365 841.831 174.796 843.314 175.21V203.478H842.469C842.469 203.478 839.918 202.633 832.903 202.633C811.013 202.633 799.103 215.594 799.103 239.828V288.295H770.836V175.21Z" fill="black"/>
<path d="M856.5 133.122H884.767V182.865C884.767 212.2 884.336 217.509 884.336 217.509H884.767L926.857 175.212H962.139L912.843 224.11L970.031 288.313H936.646L895.401 241.536L884.767 251.946V288.297H856.5V133.122Z" fill="black"/>
<path d="M970.444 211.285C970.444 163.455 1000.21 131.569 1044.85 131.569C1089.49 131.569 1119.26 163.455 1119.26 211.285C1119.26 259.114 1089.49 291.001 1044.85 291.001C1000.21 291.001 970.444 259.114 970.444 211.285ZM1088.42 211.285C1088.42 178.761 1071 156.855 1044.84 156.855C1018.67 156.855 1001.26 178.761 1001.26 211.285C1001.26 243.809 1018.69 265.715 1044.84 265.715C1070.98 265.715 1088.42 243.809 1088.42 211.285Z" fill="black"/>
<path d="M1130.08 236.656H1162.4C1162.4 254.943 1174.95 265.146 1194.08 265.146C1210.23 265.146 1221.29 257.063 1221.29 245.584C1221.29 232.622 1212.79 229.21 1185.79 223.901C1161.12 219.007 1134.98 210.716 1134.98 178.399C1134.98 151.408 1157.93 131 1193.01 131C1229.57 131 1252.11 150.132 1252.11 179.037H1219.79C1219.79 165.007 1208.95 156.286 1193.01 156.286C1176.86 156.286 1166.86 164.146 1166.86 175.625C1166.86 187.742 1173.88 192.413 1195.56 196.878C1227.65 203.685 1254.02 207.288 1254.02 243.001C1254.02 271.3 1229.36 290.432 1193.01 290.432C1156.02 290.432 1130.08 268.957 1130.08 236.656Z" fill="black"/>
<path d="M100 210C100 214.824 101.269 219.647 103.723 223.793L148.231 300.878C152.8 308.747 159.739 315.178 168.369 318.055C185.377 323.724 202.977 316.447 211.354 301.893L222.1 283.278L179.708 210L224.47 132.408L235.216 113.792C238.431 108.208 242.747 103.638 247.824 100H243.17H178.777C166.677 100 155.508 106.431 149.5 116.923L103.723 196.208C101.269 200.354 100 205.177 100 210Z" fill="#6363F1"/>
<path d="M353.847 210C353.847 205.177 352.578 200.353 350.124 196.207L305.024 118.107C296.647 103.638 279.047 96.3608 262.039 101.945C253.409 104.822 246.47 111.253 241.901 119.122L231.747 136.638L274.139 210L229.378 287.592L218.632 306.208C215.416 311.708 211.101 316.362 206.024 320H210.678H275.07C287.17 320 298.34 313.569 304.347 303.077L350.124 223.792C352.578 219.646 353.847 214.823 353.847 210Z" fill="#6363F1"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -3,9 +3,11 @@ on:
push:
branches: [ master ]
env:
ROADMAP_GA_SECRET: ${{ secrets.GA_SECRET }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PAT: ${{ secrets.PAT }}
CI: true
NEXT_TELEMETRY_DISABLED: 1
jobs:
build:
runs-on: ubuntu-latest
@@ -15,19 +17,14 @@ jobs:
persist-credentials: false
- uses: actions/setup-node@v1
with:
node-version: 18
- run: git config --global url."https://${{ secrets.PAT }}@github.com/".insteadOf ssh://git@github.com/
- uses: pnpm/action-setup@v2.2.2
with:
version: 7.13.4
node-version: 16
- name: Setup Environment
run: |
pnpm install
npm install
- name: Generate meta and build
run: |
npm run meta
npm run build
touch ./dist/.nojekyll
echo 'roadmap.sh' > ./dist/CNAME
- name: Deploy to GH Pages
run: |
git config user.email "kamranahmed.se@gmail.com"

View File

@@ -1,38 +0,0 @@
name: Update dependencies
on:
workflow_dispatch: # allow manual run
schedule:
- cron: '0 0 * * 0' # every sunday at midnight
jobs:
upgrade-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: pnpm/action-setup@v2.2.2
with:
version: 7.13.4
- name: Upgrade dependencies
run: |
pnpm install
npm run upgrade
pnpm install --lockfile-only
- name: Create PR
uses: peter-evans/create-pull-request@v4
with:
delete-branch: false
branch: "update-deps"
base: "master"
labels: |
dependencies
automated pr
reviewers: kamranahmedse
commit-message: "chore: update dependencies to latest"
title: "Upgrade dependencies to latest"
body: |
Updates all dependencies to latest versions.
Please review the changes and merge if everything looks good.

40
.gitignore vendored
View File

@@ -1,22 +1,36 @@
# build output
dist/
.output/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
out
# dependencies
node_modules/
/node_modules
/.pnp
.pnp.js
bin/developer-roadmap
# testing
/coverage
# logs
# next.js
/.next/
/out/
# production
/build
# misc
.idea
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# vercel
.vercel

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2
}

View File

@@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored
View File

@@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@@ -1,96 +0,0 @@
<p align="center">
<img src="public/brand.png" height="128">
<h2 align="center"><a href="https://roadmap.sh">roadmap.sh</a></h2>
<p align="center">Community driven roadmaps, articles and resources for developers<p>
<p align="center">
<a href="https://roadmap.sh/roadmaps">
<img src="https://img.shields.io/badge/-Roadmaps%20-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="roadmaps" />
</a>
<a href="https://youtube.com/theroadmap?sub_confirmation=1">
<img src="https://img.shields.io/badge/-Videos-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="videos" />
</a>
<a href="https://github.com/kamranahmedse/developer-roadmap/tree/0471d44c8fae58b6a36a7c57bba12253916d0249/translations">
<img src="https://img.shields.io/badge/-Translations-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="videos" />
</a>
<a href="https://www.youtube.com/channel/UCA0H2KIWgWTwpTFjSxp0now?sub_confirmation=1">
<img src="https://img.shields.io/badge/%E2%9D%A4-YouTube%20Channel-0a0a0a.svg?style=flat&colorA=0a0a0a" alt="roadmaps" />
</a>
</p>
</p>
<br>
![](https://i.imgur.com/waxVImv.png)
Roadmaps are now interactive, you can click the nodes to read more about the topics.
### [View all Roadmaps](https://roadmap.sh)
![](https://i.imgur.com/waxVImv.png)
Here is the list of available roadmaps with more being actively worked upon.
- [Frontend Roadmap](https://roadmap.sh/frontend)
- [Backend Roadmap](https://roadmap.sh/backend)
- [DevOps Roadmap](https://roadmap.sh/devops)
- [Computer Science Roadmap](https://roadmap.sh/computer-science)
- [QA Roadmap](https://roadmap.sh/qa)
- [Software Architect Roadmap](https://roadmap.sh/software-architect)
- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
- [JavaScript Roadmap](https://roadmap.sh/javascript)
- [React Roadmap](https://roadmap.sh/react)
- [Vue Roadmap](https://roadmap.sh/vue)
- [Angular Roadmap](https://roadmap.sh/angular)
- [Node.js Roadmap](https://roadmap.sh/nodejs)
- [GraphQL Roadmap](https://roadmap.sh/graphql)
- [Android Roadmap](https://roadmap.sh/android)
- [Flutter Roadmap](https://roadmap.sh/flutter)
- [Python Roadmap](https://roadmap.sh/python)
- [Go Roadmap](https://roadmap.sh/golang)
- [Java Roadmap](https://roadmap.sh/java)
- [Design System Roadmap](https://roadmap.sh/design-system)
- [DBA Roadmap](https://roadmap.sh/postgresql-dba)
- [Blockchain Roadmap](https://roadmap.sh/blockchain)
- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core)
![](https://i.imgur.com/waxVImv.png)
## Share with the community
Please consider sharing a post about [roadmap.sh](https://roadmap.sh) and the value it provides. It really does help!
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-reddit-red?logo=reddit)](https://reddit.com/submit?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-hacker%20news-orange?logo=ycombinator)](https://news.ycombinator.com/submitlink?u=https://roadmap.sh)
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-twitter-03A9F4?logo=twitter)](https://twitter.com/share?url=https://roadmap.sh&text=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-facebook-1976D2?logo=facebook)](https://www.facebook.com/sharer/sharer.php?u=https://roadmap.sh)
[![GitHub Repo stars](https://img.shields.io/badge/share%20on-linkedin-3949AB?logo=linkedin)](https://www.linkedin.com/shareArticle?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
## Development
Clone the repository, install the dependencies and start the application
```bash
git clone git@github.com:kamranahmedse/developer-roadmap.git
npm install
npm run dev
```
## Contribution
> Have a look at [contribution docs](./contributing.md) for how to update any of the roadmaps
- Add content to roadmaps
- Add new roadmaps
- Suggest changes to existing roadmaps
- Discuss ideas in issues
- Spread the word
## Thanks to all contributors ❤
<a href = "https://github.com/kamranahmedse/developer-roadmap/graphs/contributors">
<img src = "https://contrib.rocks/image?repo=kamranahmedse/developer-roadmap"/>
</a>
## License
Have a look at the [license file](./license) for details

View File

@@ -1,36 +0,0 @@
// https://astro.build/config
import sitemap from '@astrojs/sitemap';
import tailwind from '@astrojs/tailwind';
import compress from 'astro-compress';
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
export default defineConfig({
site: 'https://roadmap.sh',
markdown: {
rehypePlugins: [
[
rehypeExternalLinks,
{
target: '_blank',
},
],
],
},
integrations: [
tailwind({
config: {
applyBaseStyles: false,
},
}),
sitemap({
filter: shouldIndexPage,
serialize: serializeSitemap,
}),
compress({
css: false,
js: false,
}),
],
});

View File

@@ -1,14 +0,0 @@
const fs = require('node:fs');
const path = require('node:path');
const jsonsDir = path.join(process.cwd(), 'public/jsons');
const jsonFiles = fs.readdirSync(jsonsDir);
jsonFiles.forEach((jsonFileName) => {
console.log(`Compressing ${jsonFileName}...`);
const jsonFilePath = path.join(jsonsDir, jsonFileName);
const json = require(jsonFilePath);
fs.writeFileSync(jsonFilePath, JSON.stringify(json));
});

View File

@@ -1,120 +0,0 @@
const fs = require('fs');
const path = require('path');
// 1 - Renames each readme.md to index.md
// e.g.
// before => roadmaps/frontend/content/internet/readme.md
// after => roadmaps/frontend/content/internet/index.md
//
// 2 - Replaces the resource tags with short codes
// e.g.
// <ResourceGroupTitle>Free Content</ResourceGroupTitle>
// <BadgeLink colorScheme='yellow' badgeText='Read' href='https://www.w3schools.com/css/'>W3Schools — Learn CSS</BadgeLink>
//
// {% resources %}
// {% Blog "https://www.w3schools.com/css/", "W3Schools — Learn CSS" %}
// {% endresources %}
//
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md`
// Because we have the `/frontend` permalink serving the actual roadmap and not any content
const roadmapsDir = path.join(__dirname, '../src/roadmaps');
const roadmapDirs = fs.readdirSync(roadmapsDir);
roadmapDirs.forEach((roadmapDirName) => {
const roadmapDirPath = path.join(roadmapsDir, roadmapDirName);
const contentDirPath = path.join(roadmapDirPath, 'content');
console.log(`[Start] == Migrating ${roadmapDirName}`);
if (!fs.existsSync(contentDirPath)) {
console.log(`Content dir not found ${roadmapDirName}/content`);
return;
}
function handleContentDir(parentDirPath) {
const dirChildrenNames = fs.readdirSync(parentDirPath);
dirChildrenNames.forEach((dirChildName) => {
let dirChildPath = path.join(parentDirPath, dirChildName);
// If directory, handle the children for it
if (fs.lstatSync(dirChildPath).isDirectory()) {
handleContentDir(dirChildPath);
}
//////////////////////////////////////////////////////////
// 1 - Rename directories to remove the numbers
//////////////////////////////////////////////////////////
// let newDirChildPath = path.join(
// path.dirname(dirChildPath),
// path.basename(dirChildPath).replace(/^\d+-/, '')
// );
// fs.renameSync(dirChildPath, dirChildPath);
//////////////////////////////////////////////////////////
// 1 - Rename readme.md to index.md
//////////////////////////////////////////////////////////
if (dirChildPath.endsWith('readme.md')) {
const newFilePath = path.join(path.dirname(dirChildPath), `index.md`);
fs.renameSync(dirChildPath, newFilePath);
dirChildPath = newFilePath;
}
//////////////////////////////////////////////////////////
// 2 - Replace the resource tags with short codes
//////////////////////////////////////////////////////////
if (fs.lstatSync(dirChildPath).isFile()) {
const fileContent = fs.readFileSync(dirChildPath, 'utf-8');
let resourceLinks = [...fileContent.matchAll(/<BadgeLink.+<\/BadgeLink>/g)].map(([fullMatch]) => {
// const resourceType = fullMatch.match(/badgeText=["'](.+?)["']/)[1];
const link = fullMatch.match(/href=["'](.+?)["']/)[1];
const text = fullMatch.match(/>([^<]+)<\/BadgeLink>$/)[1];
return `- [${text.replaceAll(/['"]/g, '')}](${link})`;
});
//////////////////////////////////////////////////////////////////////
// Replace the dedicated roadmap tag with the short code
//////////////////////////////////////////////////////////////////////
// prettier-ignore
const dedicatedRegex = /<DedicatedRoadmap\s*href=['"](.+?)['"]\s*title=['"](.+?)['"]\s*description=['"].+?['"]\s*\/>/;
const dedicatedMatches = fileContent.match(dedicatedRegex);
if (dedicatedMatches) {
const [, href, title] = dedicatedMatches;
resourceLinks = [`- [Visit Dedicated ${title}](${href})`, ...resourceLinks];
}
resourceLinks = ['Visit the following resources to learn more:\n', ...resourceLinks];
resourceLinks = resourceLinks.join('\n');
let newFileContent = fileContent.replace(
/<ResourceGroupTitle>([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/,
resourceLinks
);
// In case if the resources were not wrapped in <ResourceGroupTitle>
newFileContent = newFileContent.replace(
/<BadgeLink([^<\/BadgeLink>]|\S|\s)+<\/BadgeLink>/,
resourceLinks
);
fs.writeFileSync(dirChildPath, newFileContent);
}
});
}
handleContentDir(contentDirPath);
// 3 - Removes the index.md file from within the content dir i.e. to avoid `/frontend` permalink for `/frontend/index.md`
// Because we have the `/frontend` permalink serving the actual roadmap and not any content
const contentRootFile = path.join(contentDirPath, '/index.md');
if (fs.existsSync(contentRootFile)) {
fs.rmSync(contentRootFile);
}
console.log(` == Migrated ${roadmapDirName}`);
});

View File

@@ -1,83 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const contentDirPath = path.join(__dirname, './developer-roadmap/content');
const guides = require('./developer-roadmap/content/guides.json');
const authors = require('./developer-roadmap/content/authors.json');
const guideImagesDirPath = path.join(__dirname, './developer-roadmap/public/guides');
const newGuideImagesDirPath = path.join(__dirname, '../public/guides');
// Remove the guide images directory
if (fs.existsSync(newGuideImagesDirPath)) {
fs.rmSync(newGuideImagesDirPath, { recursive: true });
}
fs.cpSync(guideImagesDirPath, newGuideImagesDirPath, { recursive: true });
// Remove the old guides directory
const newGuidesDirPath = path.join(__dirname, '../src/guides');
if (fs.existsSync(newGuidesDirPath)) {
fs.rmSync(newGuidesDirPath, { recursive: true });
}
fs.mkdirSync(newGuidesDirPath);
guides.forEach((guide) => {
const { id: guideId } = guide;
const originalGuidePath = path.join(contentDirPath, 'guides', `${guideId}.md`);
const newGuidePath = path.join(__dirname, `../src/guides/${guideId}.md`);
const guideWithoutFrontmatter = fs.readFileSync(originalGuidePath, 'utf8');
fs.copyFileSync(originalGuidePath, newGuidePath);
const guideAuthor = authors.find((author) => author.username === guide.authorUsername);
const guideFrontMatter = yaml
.stringify({
title: guide.title,
description: guide.description,
author: {
name: guideAuthor.name,
url: `https://twitter.com/${guideAuthor.twitter}`,
imageUrl: `${guideAuthor.picture}`,
},
seo: {
title: `${guide.title} - roadmap.sh`,
description: guide.description,
},
isNew: guide.isNew,
type: guide.type,
date: guide.createdAt.replace(/T.*/, ''),
sitemap: {
priority: 0.7,
changefreq: 'weekly',
},
tags: ['guide', `${guide.type}-guide`, `guide-sitemap`],
})
.replace(/date: "(.+?)"/, 'date: $1');
const guideWithUpdatedUrls = guideWithoutFrontmatter
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![]($1)]($2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![]($1)]($2)')
.replace(/\/http/g, 'http')
.replace(/]\(\/guides\/(.+?)\.png\)/g, '](/guides/$1.png)')
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const guideWithFrontmatter = `---\n${guideFrontMatter}---\n\n${guideWithUpdatedUrls}`;
console.log(`Writing guide ${guideId} to disk`);
fs.writeFileSync(newGuidePath, guideWithFrontmatter);
});
const oldAuthorAssetsPath = path.join(__dirname, 'developer-roadmap/public/authors');
const newAuthorAssetsPath = path.join(__dirname, '../public/authors');
if (fs.existsSync(newAuthorAssetsPath)) {
fs.rmSync(newAuthorAssetsPath, { recursive: true });
}
fs.cpSync(oldAuthorAssetsPath, newAuthorAssetsPath, { recursive: true });

View File

@@ -1,116 +0,0 @@
module.exports = {
angular: {
dimensions: {
width: 968,
height: 2277.8,
},
},
'aspnet-core': {
dimensions: {
width: 968,
height: 2773.45,
},
},
backend: {
dimensions: {
width: 968,
height: 2840.4,
},
},
blockchain: {
dimensions: {
width: 968,
height: 2173.87,
},
},
'computer-science': {
dimensions: {
width: 968,
height: 3009.05,
},
},
'design-system': {
dimensions: {
width: 968,
height: 2309.7,
},
},
devops: {
dimensions: {
width: 968,
height: 2527.46,
},
},
flutter: {
dimensions: {
width: 968,
height: 2042.2,
},
},
frontend: {
dimensions: {
width: 968,
height: 2734.48,
},
},
golang: {
dimensions: {
width: 968,
height: 1495.21,
},
},
java: {
dimensions: {
width: 968,
height: 1167.29,
},
},
javascript: {
dimensions: {
width: 968,
height: 2438.9,
},
},
nodejs: {
dimensions: {
width: 968,
height: 2474.06,
},
},
python: {
dimensions: {
width: 992,
height: 1259.03,
},
},
qa: {
dimensions: {
width: 968,
height: 2107.75,
},
},
react: {
dimensions: {
width: 968,
height: 1570.26,
},
},
'software-architect': {
dimensions: {
width: 968,
height: 1882.18,
},
},
'software-design-architecture': {
dimensions: {
width: 968,
height: 1764.66,
},
},
vue: {
dimensions: {
width: 968,
height: 1657.07,
},
},
};

View File

@@ -1,132 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const roadmapMetas = require('./roadmap-metas.cjs');
const oldAssetsPath = path.join(__dirname, 'developer-roadmap/public');
const newAssetsPath = path.join(__dirname, '../public/');
// Create JSONs dir
const newJsonsPath = path.join(newAssetsPath, 'jsons');
if (fs.existsSync(newJsonsPath)) {
fs.rmSync(newJsonsPath, { recursive: true });
}
fs.mkdirSync(newJsonsPath);
// Create PDFs dir
const newPdfsPath = path.join(newAssetsPath, 'pdfs');
if (fs.existsSync(newPdfsPath)) {
fs.rmSync(newPdfsPath, { recursive: true });
}
fs.mkdirSync(newPdfsPath);
const oldRoadmapsDirPath = path.join(__dirname, 'developer-roadmap/content/roadmaps');
const newRoadmapsDirPath = path.join(__dirname, '../src/roadmaps');
if (fs.existsSync(newRoadmapsDirPath)) {
fs.rmSync(newRoadmapsDirPath, { recursive: true });
}
fs.mkdirSync(newRoadmapsDirPath);
const oldRoadmaps = fs
.readdirSync(oldRoadmapsDirPath)
.map((roadmapDirName) => path.join(oldRoadmapsDirPath, roadmapDirName));
const orderInfo = {};
const typeCounter = {
role: 1,
tool: 1,
};
// Calculate the sorting information for the roadmaps
oldRoadmaps.forEach((oldRoadmapPath) => {
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase();
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json'));
orderInfo[roadmapId] = typeCounter[oldRoadmapMeta.type];
typeCounter[oldRoadmapMeta.type] += 1;
});
// Iterate and create new roadmaps
oldRoadmaps.forEach((oldRoadmapPath) => {
const roadmapId = path.basename(oldRoadmapPath).replace(/\d+-/g, '').toLowerCase();
const metaToMerge = roadmapMetas[roadmapId] ?? {};
const oldRoadmapMeta = require(path.join(oldRoadmapPath, 'meta.json'));
const isTextual = oldRoadmapMeta?.landingPath?.endsWith('.md');
const hasContentDir = fs.existsSync(path.join(oldRoadmapPath, 'content'));
const roadmapFileContent = isTextual
? fs.readFileSync(path.join(oldRoadmapPath, oldRoadmapMeta.landingPath), 'utf8')
: '';
const roadmapFileContentWithUpdatedUrls = roadmapFileContent
.replace(/\[\!\[\]\((.+?\.png)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.svg)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/\[\!\[\]\((.+?\.svg)\)\]\((.+?\.png)\)/g, '[![](/assets$1)](/assets$2)')
.replace(/assetshttp\//g, 'http')
.replace(/assetshttps:\/\//g, 'https://')
.replace(/\/http/g, 'http')
.replace(/]\(\/roadmaps\/(.+?)\.png\)/g, '](/assets/roadmaps/$1.png)')
.replace(/]\(\/roadmaps\/(.+?)\.svg\)/g, '](/assets/roadmaps/$1.svg)')
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const hasJson = fs.existsSync(path.join(oldAssetsPath, `/project/${roadmapId}.json`));
const newRoadmapMeta = {
...( hasJson ? { jsonUrl: `/jsons/${roadmapId}.json`} : {}),
pdfUrl: `/pdfs/${roadmapId}.pdf`,
order: orderInfo[roadmapId],
featuredTitle:
oldRoadmapMeta.featuredTitle === 'Software Design and Architecture'
? 'Software Design'
: oldRoadmapMeta.featuredTitle,
featuredDescription: oldRoadmapMeta.featuredDescription,
title: oldRoadmapMeta.title,
description: oldRoadmapMeta.description,
isNew: oldRoadmapMeta.isNew,
hasTopics: hasContentDir,
...metaToMerge,
seo: oldRoadmapMeta.seo,
relatedRoadmaps: oldRoadmapMeta.relatedRoadmaps,
sitemap: {
priority: 1,
changefreq: 'monthly',
},
tags: ['roadmap', 'main-sitemap', `${oldRoadmapMeta.type === 'tool' ? 'skill' : oldRoadmapMeta.type}-roadmap`],
};
const frontmatter = yaml.stringify(newRoadmapMeta);
const newRoadmapDirPath = path.join(newRoadmapsDirPath, roadmapId);
const newRoadmapFilePath = path.join(newRoadmapDirPath, `/${roadmapId}.md`);
fs.mkdirSync(newRoadmapDirPath);
fs.writeFileSync(newRoadmapFilePath, `---\n${frontmatter}---\n\n${roadmapFileContentWithUpdatedUrls}`);
const jsonFile = path.join(oldAssetsPath, oldRoadmapMeta.jsonUrl || '/unknown');
const pdfFile = path.join(oldAssetsPath, oldRoadmapMeta.pdfUrl || '/unknown');
if (fs.existsSync(jsonFile)) {
fs.copyFileSync(jsonFile, path.join(newJsonsPath, `${roadmapId}.json`));
}
if (fs.existsSync(pdfFile)) {
fs.copyFileSync(pdfFile, path.join(newPdfsPath, `${roadmapId}.pdf`));
}
// Copy the content directory
const oldRoadmapContentDir = path.join(oldRoadmapPath, 'content');
if (fs.existsSync(oldRoadmapContentDir)) {
fs.cpSync(oldRoadmapContentDir, path.join(newRoadmapDirPath, 'content'), { recursive: true });
}
});
const roadmapAssets = path.join(oldAssetsPath, 'roadmaps');
if (fs.existsSync(roadmapAssets)) {
fs.cpSync(roadmapAssets, path.join(newAssetsPath, 'roadmaps'), { recursive: true });
}

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -e
# Change working directory to the directory of this script
cd "$(dirname "$0")"
if [ ! -d "./developer-roadmap" ]; then
git clone --depth 1 -b master git@github.com:kamranahmedse/developer-roadmap.git
fi
echo "Removing old directories"
rm -rf ../src/videos
rm -rf ../src/guides
rm -rf ../src/roadmaps
rm -rf ../public/jsons
rm -rf ../public/pdfs
echo "=== Migrating Roadmaps ==="
node roadmap-migrator.cjs
echo "=== Migrating Content ==="
node content-migrator.cjs
echo "=== Migrating Guides ==="
node guide-migrator.cjs
echo "=== Migrating Videos ==="
node video-migrator.cjs

View File

@@ -1,58 +0,0 @@
const fs = require('fs');
const path = require('path');
const yaml = require('json-to-pretty-yaml');
const contentDirPath = path.join(__dirname, './developer-roadmap/content');
const videos = require('./developer-roadmap/content/videos.json');
// Remove the old videos directory
const newVideosDirPath = path.join(__dirname, '../src/videos');
if (fs.existsSync(newVideosDirPath)) {
fs.rmSync(newVideosDirPath, { recursive: true });
}
fs.mkdirSync(newVideosDirPath);
videos.forEach((video) => {
const { id: videoId } = video;
const originalVideoPath = path.join(
contentDirPath,
'videos',
`${videoId}.md`
);
const newVideoPath = path.join(__dirname, `../src/videos/${videoId}.md`);
const videoWithoutFrontmatter = fs.readFileSync(originalVideoPath, 'utf8');
fs.copyFileSync(originalVideoPath, newVideoPath);
const videoFrontMatter = yaml
.stringify({
title: video.title,
description: video.description,
duration: video.duration,
isNew: video.isNew,
date: video.createdAt.replace(/T.*/, ''),
author: {
name: 'Kamran Ahmed',
url: `https://twitter.com/kamranahmedse`,
imageUrl: `/authors/kamranahmedse.jpeg`,
},
sitemap: {
priority: 0.7,
changefreq: 'weekly',
},
tags: ['video', `video-sitemap`],
})
.replace(/date: "(.+?)"/, 'date: $1');
const videoWithIframeClass = videoWithoutFrontmatter
.replace(/<iframe/g, '<iframe class="w-full aspect-video mb-5"')
.replace(/<iframe(.+?)\s?\/>/g, '<iframe$1></iframe>');
const videoWithFrontmatter = `---\n${videoFrontMatter}---\n\n${videoWithIframeClass}`;
console.log(`Writing video ${videoId} to disk`);
fs.writeFileSync(newVideoPath, videoWithFrontmatter);
});

View 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'>&middot;</Text>
</>
)}
<Text color='gray.500' as='span'>{formattedDate}</Text>
{subLink?.text && (
<>
<Text d={['none', 'none', 'inline']} mx='7px' color='gray.500' as='span'>&middot;</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>
);
}

68
components/custom-ad.tsx Normal file
View File

@@ -0,0 +1,68 @@
import { Box, Flex, Heading, Image, Link } from '@chakra-ui/react';
import { event } from '../lib/gtag';
function getPageSlug() {
const pathname = (typeof window !== 'undefined' ? window : {} as any)?.location?.pathname || '';
return pathname?.replace(/\//g, '');
}
export const CustomAd = () => {
const slug = getPageSlug();
if (slug !== 'devops') {
return null;
}
return (
<Link
href='https://thenewstack.io/ebooks/devops/cloud-native-devops-2019/?utm_source=Roadmap.sh&utm_medium=Banner&utm_campaign=Roadmap+DevOps+Ebook'
id='custom-ad'
pos='fixed'
bottom='15px'
right='20px'
zIndex={999}
display='flex'
maxWidth='330px'
bg='white'
boxShadow='0 1px 4px 1px hsla(0, 0%, 0%, .1)'
_hover={{ textDecoration: 'none' }}
rel="noopener sponsored"
target={'_blank'}
onClick={() => {
event({
category: 'SponsorClick',
action: `TNS EBook Redirect`,
label: `Clicked TNS EBook Link`
});
}}
>
<Image
src='https://i.imgur.com/fEKq19S.png'
alt='Custom Logo'
height={['100px', '100px', '100px', 'auto']}
width='130'
style={{ maxWidth: '130px', border: 'none' }}
/>
<Flex as='span' flexDirection='column' justifyContent='space-between'>
<Box as='span' p='10px'>
<Heading as='span' fontSize='14px' mb='5px' display='block'>Free DevOps eBook</Heading>
<Box display='block' as='span' fontSize='13px' lineHeight={1.5} fontWeight={500} color='gray.500'>
Learn all about doing DevOps the Cloud Native way with this free ebook from our partner, The New Stack
</Box>
</Box>
<Box as='span'
textAlign='center'
fontWeight={600}
fontSize='9px'
letterSpacing='0.5px'
textTransform='uppercase'
padding='5px 10px'
display={'block'}
background='repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .025) 5px, hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4)'
>
Partner Content
</Box>
</Flex>
</Link>
);
};

View 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'>&rarr;</Text>
</Link>
</Box>
);
}

70
components/footer.tsx Normal file
View File

@@ -0,0 +1,70 @@
import { Box, Container, Flex, Image, Link, Stack, Text } from '@chakra-ui/react';
import siteConfig from '../content/site.json';
import { CustomAd } from './custom-ad';
import React from 'react';
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='/about'>About</Link>
<Link py='7px' _hover={{ color: 'white' }} target='_blank'
href={siteConfig.url.youtube}>YouTube</Link>
</Stack>
</>
);
}
export function Footer() {
return (
<Box bg='brand.hero' 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'>&copy; roadmap.sh</Text>&middot;
<Link href='/about' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
mx='10px'>FAQs</Link>&middot;
<Link href='/terms' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
mx='10px'>Terms</Link>&middot;
<Link href='/privacy' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
mx='10px'>Privacy</Link>
</Text>
</Box>
</Container>
<CustomAd />
</Box>
);
}

View File

@@ -0,0 +1,117 @@
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'} />
<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={999}
>
<Link href='/roadmaps'>Roadmaps</Link>
<Link href='/guides'>Guides</Link>
<Link href='/watch'>Videos</Link>
<Link href='/signup'>Subscribe</Link>
<CloseButton onClick={() => setIsOpen(false)} pos='fixed' top='40px' right='15px' size='lg' />
</Stack>
)}
</>
);
}
type GlobalHeaderProps = {
variant?: 'transparent' | 'solid'
};
export function GlobalHeader(props: GlobalHeaderProps) {
const { variant = 'solid' } = props;
return (
<Box bg={variant === 'solid' ? 'gray.900' : 'transparent'} p='20px 0'>
<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>
);
}

View File

@@ -0,0 +1,33 @@
import { Badge, Box, Heading, Link, Text } from '@chakra-ui/react';
import { GuideType } from '../../lib/guide';
type GuideGridItemProps = {
title: string;
href: string;
subtitle: string;
date: string;
isNew?: boolean;
colorIndex?: number;
type?: GuideType['type'];
};
const bgColorList = [
'gray.700',
'purple.800'
];
export function GuideGridItem(props: GuideGridItemProps) {
const { title, subtitle, date, isNew = false, colorIndex = 0, href, type } = 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' textTransform='capitalize'>
{isNew && <Badge colorScheme={'green'} mr='10px'>New</Badge>}
{type} Guide
</Text>
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
</Box>
);
}

167
components/helmet.tsx Normal file
View File

@@ -0,0 +1,167 @@
import NextHead from 'next/head';
import siteConfig from '../content/site.json';
import { RoadmapType } from '../lib/roadmap';
type HelmetProps = {
title?: string;
keywords?: string[];
canonical?: string;
description?: string;
noIndex?: boolean;
roadmap?: RoadmapType;
};
function getRichSnippetJson(roadmap: RoadmapType) {
return {
'@context': 'https://schema.org',
'@type': 'Article',
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://roadmap.sh/${roadmap.id}`,
},
headline: roadmap.seo.title,
description: roadmap.seo.description,
image: roadmap.jsonUrl
? `https://roadmap.sh/roadmaps/${roadmap.id}.png`
: undefined,
author: {
'@type': 'Person',
name: 'Kamran Ahmed',
url: 'https://twitter.com/kamranahmedse',
},
publisher: {
'@type': 'Organization',
name: 'roadmap.sh',
logo: {
'@type': 'ImageObject',
url: 'https://roadmap.sh/brand-square.png',
},
},
};
}
const Helmet = (props: HelmetProps) => {
const { roadmap, title, canonical, description, keywords, noIndex = false } = props;
return (
<NextHead>
<meta charSet="UTF-8" />
<title>{title || siteConfig.title}</title>
<meta
name="description"
content={description || siteConfig.description}
/>
<meta name="author" content={siteConfig.author} />
<meta
name="keywords"
content={keywords ? keywords.join(',') : siteConfig.keywords.join(',')}
/>
{noIndex && <meta name="robots" content="noindex" /> }
<meta
name="viewport"
content="width=device-width, user-scalable=yes, initial-scale=1.0, maximum-scale=3.0, minimum-scale=1.0"
/>
{canonical && <link rel="canonical" href={canonical} />}
<meta httpEquiv="Content-Language" content="en" />
<meta property="og:title" content={title || siteConfig.title} />
<meta
property="og:description"
content={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={title || siteConfig.title} />
<meta
name="twitter:description"
content={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" />
{roadmap?.id && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(getRichSnippetJson(roadmap)),
}}
/>
)}
{/* 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;

View File

@@ -0,0 +1,67 @@
import { RoadmapType } from '../../lib/roadmap';
import { SimpleGrid, Tag } from '@chakra-ui/react';
import { HomeRoadmapItem } from '../roadmap/home-roadmap-item';
type FeaturedRoadmapsListProps = {
roadmaps: RoadmapType[];
title: string;
};
export const upcomingRoadmaps = [
{
type: 'Role Based',
title: 'React Native',
description: 'Step by step guide to become a React Native Developer',
id: 'react-native'
},
{
type: 'Skill Based',
title: 'TypeScript',
description: 'Step by step guide to learn TypeScript in 2022',
id: 'typescript'
},
{
type: 'Skill Based',
title: 'Rust',
description: 'Step by step guide to learn Rust in 2022',
id: 'rust'
},
];
export function FeaturedRoadmapsList(props: FeaturedRoadmapsListProps) {
const { roadmaps, title } = props;
return (
<>
<Tag bg='gray.400' mb={4}>{title}</Tag>
<SimpleGrid columns={[1, 2, 3]} spacing={['10px', '10px', '15px']} mb='40px'>
<>
{roadmaps.map((roadmap: RoadmapType, counter: number) => (
<HomeRoadmapItem
isUpcoming={roadmap.isUpcoming}
url={`/${roadmap.id}`}
key={roadmap.id}
colorIndex={counter}
title={roadmap.featuredTitle === 'Software Design and Architecture' ? 'Software Design' : roadmap.featuredTitle}
isCommunity={roadmap.isCommunity}
isNew={roadmap.isNew}
subtitle={roadmap.featuredDescription}
/>
))}
{upcomingRoadmaps
.filter(roadmap => roadmap.type === title)
.map((roadmap, counter) => (
<HomeRoadmapItem
isUpcoming={true}
url={`/upcoming?id=${roadmap.id}`}
key={`upcoming-${roadmap.id}`}
colorIndex={9}
title={roadmap.title}
subtitle={roadmap.description}
/>
))}
</>
</SimpleGrid>
</>
);
}

View File

@@ -0,0 +1,3 @@
<svg width="29" height="29">
<path d="M23.2 5H5.8a.8.8 0 0 0-.8.8V23.2c0 .44.35.8.8.8h9.3v-7.13h-2.38V13.9h2.38v-2.38c0-2.45 1.55-3.66 3.74-3.66 1.05 0 1.95.08 2.2.11v2.57h-1.5c-1.2 0-1.48.57-1.48 1.4v1.96h2.97l-.6 2.97h-2.37l.05 7.12h5.1a.8.8 0 0 0 .79-.8V5.8a.8.8 0 0 0-.8-.79"></path>
</svg>

After

Width:  |  Height:  |  Size: 298 B

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 841 B

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
<path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 474 B

View 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

View File

@@ -0,0 +1 @@
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22 18v-7h-9v-5h3v-6h-8v6h3v5h-9v7h-2v6h6v-6h-2v-5h7v5h-2v6h6v-6h-2v-5h7v5h-2v6h6v-6z"/></svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@@ -0,0 +1,3 @@
<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>

After

Width:  |  Height:  |  Size: 550 B

View 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>
);
}

View 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

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { Badge, Flex, Link, Text } from '@chakra-ui/react';
type LinksListItemProps = {
href: string;
title: string;
subtitle: string;
badgeText?: string;
target?: string;
icon?: React.ReactChild;
hideSubtitleOnMobile?: boolean;
};
export function LinksListItem(props: LinksListItemProps) {
const { title, subtitle, badgeText, icon, hideSubtitleOnMobile = false, href, target } = props;
return (
<Link
target={target || '_self'}
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'}
sx={{
'@media (hover: none)': {
'&:hover': {
'& .list-item-title': {
transform: 'none'
}
}
}
}}
_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='green' 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
View 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>
);
}

View 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>
);
};

View File

@@ -0,0 +1,37 @@
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
);
const linkProps: Record<string, string> = {
target: '_self',
...(isExternalUrl
? {
rel: 'nofollow',
target: '_blank',
}
: {}),
};
return (
<Link href={props.href} {...linkProps}>
{props.children}
</Link>
);
};
export default EnrichedLink;

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { Link, Text, Badge } from '@chakra-ui/react';
type BadgeLinkType = {
target: string;
badgeText: string;
href: string;
colorScheme?: string;
children: React.ReactNode;
};
export function BadgeLink(props: BadgeLinkType) {
const {
target = '_blank',
colorScheme = 'purple',
badgeText,
href,
children,
} = props;
// Is external URL or is a media URL
const isExternalUrl = /(^http(s)?:\/\/)|(\.(png|svg|jpeg|jpg)$)/.test(
props.href
);
const linkProps: Record<string, string> = {
...(isExternalUrl
? {
rel: 'nofollow',
}
: {}),
};
return (
<Text mb={'0px'}>
<Link
fontSize="14px"
color="blue.700"
fontWeight={500}
textDecoration="none"
href={href}
target={target}
_hover={{ textDecoration: 'none', color: 'purple.400' }}
{...linkProps}
>
<Badge fontSize="11px" mr="7px" colorScheme={colorScheme}>
{badgeText}
</Badge>
{children}
</Link>
</Text>
);
}

View File

@@ -0,0 +1,27 @@
import styled from 'styled-components';
const BlockQuote = styled.blockquote`
padding: 16px 20px;
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;
}
}
`;
export default BlockQuote;

View 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>;
}

View File

@@ -0,0 +1,22 @@
import { Box, Flex, Heading, Text } from '@chakra-ui/react';
import TreeIcon from '../../icons/tree.svg';
type DedicatedRoadmapProps = {
href: string;
title: string;
description: string;
};
export function DedicatedRoadmap(props: DedicatedRoadmapProps) {
const { href, title, description } = props;
return (
<Flex as={'a'} target='_blank' href={ href } p={5} px={5} mt={6} rounded='md' alignItems='center' _hover={{ bg: 'yellow.400'}} bg='yellow.300'>
<Box d={['none', 'none', 'none', 'block', 'block']} mr={4} height='32px' w='32px' as={TreeIcon} color='gray.900' />
<Box as='span'>
<Heading fontSize='lg' as={'h4'} mb='2px' color='gray.900'>{ title }</Heading>
<Text color='gray.700' as='span' fontSize='md'>{ description }</Text>
</Box>
</Flex>
);
}

View File

@@ -0,0 +1,81 @@
import React from 'react';
import styled from 'styled-components';
import LinkIcon from 'components/icons/link.svg';
const linkify = (Component: React.FunctionComponent<any>) => {
return function EnrichedHeading(props: { children: string }): React.ReactNode {
const text = props.children;
const id = text?.toLowerCase && text
.toLowerCase()
.replace(/[^\x00-\x7F]/g, '')
.replace(/\s+/g, '-')
.replace(/[?!]/g, '');
return (
<Component id={id}>
<HeaderLink href={`#${id}`}>
<LinkIcon />
</HeaderLink>
{props.children}
</Component>
);
};
};
const HeaderLink = styled.a`
position: absolute;
top: 0;
left: -25px;
width: 25px;
display: none;
height: 100%;
align-items: center;
justify-content: flex-start;
`;
const H1 = styled.h1`
position: relative;
font-size: 32px;
line-height: 40px;
font-weight: 700;
margin: 20px 0 10px !important;
&:hover ${HeaderLink} {
display: flex;
}
`;
const H2 = styled(H1).attrs({ as: 'h2' })`
font-size: 30px;
`;
const H3 = styled(H1).attrs({ as: 'h3' })`
margin: 22px 0 8px;
font-size: 28px;
`;
const H4 = styled(H1).attrs({ as: 'h4' })`
margin: 18px 0 8px;
font-size: 24px;
`;
const H5 = styled(H1).attrs({ as: 'h5' })`
margin: 14px 0 8px;
font-size: 18px;
`;
const H6 = styled(H1).attrs({ as: 'h6' })`
margin: 12px 0 8px;
font-size: 18px;
`;
const Headings = {
h1: H1,
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6
};
export default Headings;

View 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>
);
}

View File

@@ -0,0 +1,7 @@
import styled from 'styled-components';
export const Img = styled.img`
max-width: 100%;
margin: 25px auto;
display: block;
`;

View File

@@ -0,0 +1,34 @@
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 { Img } from './img';
import EnrichedLink from './a';
import { BadgeLink } from './badge-link';
import { Li, Ul } from './ul';
import PremiumBlock from './premium-block';
import { ResourceGroupTitle } from './resource-group-title';
import { DedicatedRoadmap } from './dedicated-roadmap';
const MdxComponents = {
p: P,
...Headings,
pre: Pre,
blockquote: BlockQuote,
a: EnrichedLink,
DedicatedRoadmap,
table: Table,
iframe: IFrame,
img: Img,
code: Code,
BadgeLink: BadgeLink,
ResourceGroupTitle: ResourceGroupTitle,
PremiumBlock: PremiumBlock,
ul: Ul,
li: Li
};
export default MdxComponents;

View 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;
`;

View File

@@ -0,0 +1,12 @@
import styled from 'styled-components';
export const Pre = styled.pre`
margin: 25px -25px 25px -25px !important;
padding: 20px 25px !important;
border-radius: 10px;
line-height: 1.5 !important;
code {
background: transparent;
}
`;

View 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>
);
}

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { Heading } from '@chakra-ui/react';
type ResourceGroupTitleProps = {
children: React.ReactNode;
};
export function ResourceGroupTitle(props: ResourceGroupTitleProps) {
const { children } = props;
return <Heading mt='20px' color='gray.800' fontSize='14px' pb='5px' borderBottomWidth={1} textTransform='uppercase' as="h2" mb={'10px'}>{children}</Heading>;
}

View File

@@ -0,0 +1,25 @@
import styled from 'styled-components';
export const Table = styled.table`
border-collapse: separate;
width: 100%;
border-spacing: 0;
margin: 20px 0;
th {
color: #666;
font-size: 12px;
font-weight: 400;
background: #FAFAFA;
text-transform: uppercase;
height: 40px;
vertical-align: middle;
padding: 5px 10px;
}
td {
font-size: 14px;
padding: 10px;
border-bottom: 1px solid #EAEAEA;
}
`;

View File

@@ -0,0 +1,16 @@
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;
ul {
margin-top: 18px;
}
`;
export const Li = styled.li`
margin-bottom: 7px;
`;

View File

@@ -0,0 +1,29 @@
import { Box, Container, Heading, Link, Text } from '@chakra-ui/react';
export function OpensourceBanner() {
return (
<Box bg='white' borderTopWidth={1} py={['45px', '45px', '70px']} textAlign='center'>
<Container maxW='container.sm'>
<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,&nbsp;
<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}
>6th 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' }}
title='GitHub'
/>
</Container>
</Box>
);
}

View File

@@ -0,0 +1,37 @@
import { Box, Container, Heading, Text } from '@chakra-ui/react';
import React from 'react';
type PageHeaderProps = {
title: string;
subtitle: string;
children?: React.ReactNode;
beforeTitle?: React.ReactNode;
};
export function PageHeader(props: PageHeaderProps) {
const { title, subtitle, children, beforeTitle = null } = props;
return (
<Box pt={['25px', '20px', '45px']} pb={['20px', '15px', '30px']} borderBottomWidth={1} mb='30px'>
<Container maxW='container.md' position='relative'>
{beforeTitle}
<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>
);
}

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
type PageWrapperProps = {
children: React.ReactNode;
}
export function PageWrapper(props: PageWrapperProps) {
const { children } = props;
return (
<Box bgColor='brand.bg' bgImage='url(/bg.jpg)' bgRepeat='no-repeat' bgSize='100%' w='100%' minH='100vh'>
{ children }
</Box>
);
}

View File

@@ -0,0 +1,80 @@
import { Badge, Box, Button, Container, Link, Stack, Text } from '@chakra-ui/react';
import { RoadmapType } from '../lib/roadmap';
type RelatedRoadmapsProps = {
roadmaps: RoadmapType[];
};
const colorsList = [
'gray.700',
'purple.700',
'blue.700',
'red.700',
'green.700',
'teal.700',
'yellow.700',
'cyan.700',
'pink.700'
];
const roadmapTitleMapping: Record<string, string> = {
"Software Design and Architecture": "Software Design",
}
export function RelatedRoadmaps(props: RelatedRoadmapsProps) {
const { roadmaps } = props;
if (!roadmaps.length) {
return null;
}
return (
<Box borderTopWidth={1} bgColor='gray.50' pb='35px' pt='5px'>
<Container maxW='container.md'>
<Box display='flex' position='relative' top='-23px' alignItems='center' justifyContent='space-between'>
<Text textAlign='center' borderWidth={1} bg='white' p='4px' fontWeight='bold' rounded='md' px={'15px'}>
Related Roadmaps
</Text>
<Button as={Link} variant='outline' bg='white' size='sm' _hover={{ textDecoration: 'none', bg: 'gray.100' }}
href='/'>
<Text as='span' display={['inline', 'none', 'none']}>More &rarr;</Text>
<Text as='span' display={['none', 'inline', 'inline']}>All Roadmaps &rarr;</Text>
</Button>
</Box>
<Stack spacing='5px'>
{roadmaps.map((roadmap, counter) => (
<Link
href={`/${roadmap.id}`}
key={roadmap.id}
borderWidth={1}
borderColor='blue.100'
py='7px'
px='14px'
rounded='md'
bg='white'
textDecoration={'none'}
_hover={{ bg: 'gray.100', borderColor: 'blue.200' }}
bgGradient='linear(to-r, white, gray.50)'
display='flex'
alignItems='center'
flexDir={['column', 'row', 'row']}
>
<Text
color={colorsList[counter]}
as='span'
fontWeight='bold'
display={['inline-block']}
minWidth='150px'
mr='10px'
>
{roadmapTitleMapping[roadmap.featuredTitle] || roadmap.featuredTitle}
</Text>
<Text as='span' display={['block', 'inline']} isTruncated maxWidth='100%' fontSize={['sm', 'sm', 'md']} color='gray.700'>{roadmap.featuredDescription}</Text>
</Link>
))}
</Stack>
</Container>
</Box>
);
}

View File

@@ -0,0 +1,117 @@
import { Box, Button, Flex, Text } from '@chakra-ui/react';
import { RemoveScroll } from 'react-remove-scroll';
import { RoadmapType } from '../../lib/roadmap';
import RoadmapGroup from '../../pages/[roadmap]/[group]';
import { CheckIcon, CloseIcon, RepeatIcon } from '@chakra-ui/icons';
import { queryGroupElementsById } from '../../lib/renderer';
type ContentDrawerProps = {
roadmap: RoadmapType;
groupId: string;
onClose?: () => void;
};
export function ContentDrawer(props: ContentDrawerProps) {
const { roadmap, groupId, onClose = () => null } = props;
if (!groupId) {
return null;
}
const isDone = localStorage.getItem(groupId) === 'done';
return (
<Box zIndex={99999} pos="relative">
<Box
onClick={onClose}
pos="fixed"
top={0}
left={0}
right={0}
bottom={0}
bg="black"
opacity={0.4}
/>
<RemoveScroll allowPinchZoom>
<Box
p="0px 30px 30px"
position="fixed"
w={['100%', '60%', '40%']}
bg="white"
top={0}
right={0}
bottom={0}
borderLeftWidth={'1px'}
overflowY="scroll"
>
<Flex
mt="20px"
justifyContent="space-between"
alignItems="center"
zIndex={1}
>
{!isDone && (
<Button
onClick={() => {
localStorage.setItem(groupId, 'done');
queryGroupElementsById(groupId).forEach((item) =>
item?.classList?.add('done')
);
onClose();
}}
colorScheme="green"
leftIcon={<CheckIcon />}
size="xs"
iconSpacing={0}
>
<Text
as="span"
d={['block', 'none', 'none', 'block']}
ml="10px"
>
Mark as Done
</Text>
</Button>
)}
{isDone && (
<Button
onClick={() => {
localStorage.removeItem(groupId);
queryGroupElementsById(groupId).forEach((item) =>
item?.classList?.remove('done')
);
onClose();
}}
colorScheme="red"
leftIcon={<RepeatIcon />}
size="xs"
iconSpacing={0}
>
<Text
as="span"
d={['block', 'none', 'none', 'block']}
ml="10px"
>
Mark as Pending
</Text>
</Button>
)}
<Button
onClick={onClose}
colorScheme="yellow"
ml="5px"
leftIcon={<CloseIcon width="8px" />}
iconSpacing={0}
size="xs"
>
<Text as="span" d={['none', 'none', 'none', 'block']} ml="10px">
Close
</Text>
</Button>
</Flex>
<RoadmapGroup isOutlet roadmap={roadmap} group={groupId} />
</Box>
</RemoveScroll>
</Box>
);
}

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { Box, Button, Divider, Link, Text } from '@chakra-ui/react';
type EditContentPageLinkProps = {
href: string;
};
export function EditContentPageLink(props: EditContentPageLinkProps) {
const { href } = props;
return (
<Box my='30px'>
<Divider mb="15px" orientation="horizontal" />
<Text
lineHeight="23px"
fontWeight={500}
fontSize="14px"
color="gray.500"
mb="10px"
>
This page is a work in progress. Help us by writing a small
introduction to the topic and suggesting a few links to read more
about this topic.
</Text>
<Button
size="sm"
py="20px"
as={Link}
href={href}
target="_blank"
isFullWidth
colorScheme={'gray'}
_hover={{ textDecoration: 'none', bg: 'gray.200' }}
>
Edit this Page
</Button>
</Box>
);
}

View File

@@ -0,0 +1,121 @@
import { Badge, Box, Flex, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
import { InfoIcon } from '@chakra-ui/icons';
type RoadmapGridItemProps = {
title: string;
subtitle: string;
isCommunity?: boolean;
isUpcoming?: boolean;
colorIndex?: number;
isNew?: boolean;
url: string;
};
const bgColorList = [
'red.100',
'yellow.100',
'green.200',
'teal.200',
'blue.200',
'red.200',
'gray.200',
'teal.200',
'yellow.100',
'green.200',
'red.200'
];
export function HomeRoadmapItem(props: RoadmapGridItemProps) {
const {
title,
subtitle,
isCommunity,
isNew,
colorIndex = 0,
url,
isUpcoming
} = props;
return (
<Box
position='relative'
as={Link}
href={url}
_hover={{
textDecoration: 'none',
bg: 'rgba(255,255,255,.10)'
}}
sx={{
// On mobile devices, don't change the scale
'@media (hover: none)': {
'&:hover': {
bg: 'rgba(255,255,255,.05)'
}
}
}}
flex={1}
shadow='2xl'
className={'home-roadmap-item'}
bg={'rgba(255,255,255,.05)'}
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']}
color={bgColorList[colorIndex]}
mb='5px'
d='flex'
alignItems='center'
>
{title}
{ isNew && <Badge position='absolute' bottom={0} right={0} colorScheme='yellow' ml='10px'>New</Badge> }
</Heading>
<Text color='gray.200' fontSize={['13px']}>
{subtitle}
</Text>
{isUpcoming && (
<Flex
alignItems='center'
justifyContent='center'
pos='absolute'
left={0}
right={0}
top={0}
bottom={0}
rounded='10px'
>
<Text
color='white'
bg='gray.600'
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>
);
}

View File

@@ -0,0 +1,50 @@
import { Badge, Link, Text } from '@chakra-ui/react';
import siteConfig from '../../content/site.json';
import { event } from '../../lib/gtag';
import React from 'react';
export function NewAlertBanner() {
return (
<Text
_hover={{
textDecoration: 'none',
color: 'blue.700',
'& .new-badge': { bg: 'blue.700' },
}}
as={Link}
href={siteConfig.url.youtube}
d="block"
target="_blank"
color="red.700"
fontSize="sm"
mb="10px"
fontWeight={500}
onClick={() =>
event({
category: 'Subscription',
action: 'Clicked the YouTube banner',
label: 'YouTube Alert on Roadmap',
})
}
>
<Badge
transition={'all 300ms'}
className="new-badge"
mr="7px"
colorScheme="red"
variant="solid"
>
New
</Badge>
<Text textDecoration="underline" as="span" d={['none', 'inline']}>
Roadmap topics to be covered on our YouTube Channel
</Text>
<Text textDecoration="underline" as="span" d={['inline', 'none']}>
Topic videos being made on YouTube
</Text>
<Text as="span" ml="5px">
&raquo;
</Text>
</Text>
);
}

View File

@@ -0,0 +1,26 @@
import { RoadmapType } from '../../lib/roadmap';
import { Container, Heading, Link, Text } from '@chakra-ui/react';
import siteConfig from '../../content/site.json';
type RoadmapProps = {
roadmap: RoadmapType;
};
export function RoadmapError(props: RoadmapProps) {
const { roadmap } = props;
return (
<Container
bg={'red.600'}
maxW={'container.md'}
position="relative"
mt="50px"
p='20px'
rounded='5px'
color='white'
>
<Heading mb='4px' size='md'>Oops! There&apos;s an error</Heading>
<Text>Try refreshing or <Link target='_blank' fontWeight={700} textDecoration={'underline'} fontSize='14px' href={siteConfig.url.issue}>report a bug</Link> and use the <Link fontWeight={700} textDecoration={'underline'} href={`/roadmaps/${roadmap.id}.png`}>non-interactive version</Link></Text>
</Container>
);
}

View 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>
);
}

View File

@@ -0,0 +1,20 @@
import { Container, Spinner } from '@chakra-ui/react';
export function RoadmapLoader() {
return (
<Container
maxW={'container.md'}
position="relative"
mt="60px"
textAlign="center"
>
<Spinner
thickness="7px"
speed="0.65s"
emptyColor="gray.200"
color="gray.500"
size="xl"
/>
</Container>
);
}

View File

@@ -0,0 +1,220 @@
import siteConfig from '../../content/site.json';
import { isInteractiveRoadmap, RoadmapType } from '../../lib/roadmap';
import { NewAlertBanner } from './new-alert-banner';
import {
Badge,
Box,
Button,
Container,
Flex,
Heading,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
Stack,
Text,
useDisclosure
} from '@chakra-ui/react';
import { AtSignIcon, ChatIcon, DownloadIcon } from '@chakra-ui/icons';
import React from 'react';
import { SIGNUP_EMAIL_INPUT_NAME, SIGNUP_FORM_ACTION } from '../../pages/signup';
import { event } from '../../lib/gtag';
type RoadmapPageHeaderType = {
roadmap: RoadmapType;
};
function RoadmapDownloader({ roadmapTitle }: { roadmapTitle: string }) {
const { isOpen, onOpen, onClose } = useDisclosure();
const initialRef = React.useRef(null);
return (
<>
<Button
onClick={(e) => {
event({
category: 'Subscription',
action: `Clicked Download ${roadmapTitle} Roadmap`,
label: `Download ${roadmapTitle} Roadmap Button`
});
onOpen();
}}
size='xs'
py='14px'
px='10px'
leftIcon={<DownloadIcon />}
display={['none', 'flex']}
colorScheme='yellow'
variant='solid'
_hover={{ textDecoration: 'none' }}
_focus={{ boxShadow: 'none' }}
>
Download
</Button>
<Modal initialFocusRef={initialRef} closeOnOverlayClick={true} isOpen={isOpen} onClose={onClose} isCentered motionPreset='none'>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody p={6}>
<Heading mb='5px' fontSize='2xl'>Download Roadmap</Heading>
<Text fontSize={'md'} color='gray.700'>Enter your email below to receive the download link.</Text>
<form action={SIGNUP_FORM_ACTION} method='post' target='_blank' onSubmit={() => {
event({
category: 'Subscription',
action: `Submitted Download ${roadmapTitle} Roadmap Email`,
label: `PDF / Subscribe ${roadmapTitle} Roadmap`
});
onClose();
}}>
<Input required ref={initialRef} size='md' my='10px' type='email' placeholder='Email address' name={SIGNUP_EMAIL_INPUT_NAME} />
<Button type='submit' colorScheme='green' size='md' width={'full'}>Send Link</Button>
</form>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}
function RoadmapSubscriber({ roadmapTitle }: { roadmapTitle: string }) {
const { isOpen, onOpen, onClose } = useDisclosure();
const initialRef = React.useRef(null);
return (
<>
<Button
onClick={(e) => {
event({
category: 'Subscription',
action: `Clicked Subscribe ${roadmapTitle} Roadmap`,
label: `Subscribe ${roadmapTitle} Roadmap Button`
});
onOpen();
}}
size='xs'
py='14px'
px='10px'
leftIcon={<AtSignIcon />}
display={'flex'}
colorScheme='yellow'
variant='solid'
_hover={{ textDecoration: 'none' }}
_focus={{ boxShadow: 'none' }}
>
Subscribe
</Button>
<Modal initialFocusRef={initialRef} closeOnOverlayClick={true} isOpen={isOpen} onClose={onClose} isCentered motionPreset='none'>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody p={6}>
<Heading mb='5px' fontSize='2xl'>Subscribe</Heading>
<Text fontSize={'md'} color='gray.700'>Enter your below to receive updates to this roadmap.</Text>
<form action={SIGNUP_FORM_ACTION} method='post' target='_blank' onSubmit={() => {
event({
category: 'Subscription',
action: `Submitted Subscribe ${roadmapTitle} Roadmap Email`,
label: `Email / Subscribe ${roadmapTitle} Roadmap`
});
onClose();
}}>
<Input required ref={initialRef} size='md' my='10px' type='email' placeholder='Email address' name={SIGNUP_EMAIL_INPUT_NAME} />
<Button type='submit' colorScheme='green' size='md' width={'full'}>Subscribe</Button>
</form>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}
export function RoadmapPageHeader(props: RoadmapPageHeaderType) {
const { roadmap } = props;
return (
<Box
pt={['25px', '20px', '45px']}
pb={['20px', '15px', '30px']}
borderBottomWidth={1}
mb='30px'
>
<Container maxW='container.md' position='relative'>
<NewAlertBanner />
<Heading
as='h1'
color='black'
fontSize={['28px', '33px', '40px']}
fontWeight={700}
mb={['2px', '2px', '5px']}
>
{roadmap.title}
</Heading>
<Text fontSize={['13px', '14px', '15px']}>{roadmap.description}</Text>
<Flex justifyContent='space-between' alignItems={'center'} mt='20px'>
<Stack isInline flex={1}>
<Button
d={['flex', 'flex']}
as={Link}
href={'/roadmaps'}
size='xs'
py='14px'
px='10px'
colorScheme='teal'
variant='solid'
_hover={{ textDecoration: 'none' }}
>
&larr;
<Text as='span' d={['none', 'inline']} ml='5px'>
All Roadmaps
</Text>
</Button>
<RoadmapDownloader roadmapTitle={roadmap.featuredTitle} />
<RoadmapSubscriber roadmapTitle={roadmap.featuredTitle} />
<Box flex={1} justifyContent='flex-end' d='flex'>
<Button
as={Link}
href={`${siteConfig.url.issue}?title=[Suggestion] ${roadmap.title}`}
target='_blank'
size='xs'
py='14px'
px='10px'
colorScheme='green'
leftIcon={<ChatIcon />}
_hover={{ textDecoration: 'none' }}
>
Suggest Changes
</Button>
</Box>
</Stack>
</Flex>
{isInteractiveRoadmap(roadmap.id) && (
<Text
mt='30px'
mb={['-37px', '-32px', '-47px']}
fontWeight={500}
fontSize='14px'
bg='white'
borderWidth={1}
p='5px 7px'
rounded='3px'
>
<Badge pos='relative' top={'-1px'} mr='6px' colorScheme='yellow'>
New
</Badge>
Resources are here, try clicking any nodes.
</Text>
)}
</Container>
</Box>
);
}

View File

@@ -0,0 +1,33 @@
import { Flex, Link, Text } from '@chakra-ui/react';
import YouTubeLogo from '../components/icons/youtube.svg';
import siteConfig from '../content/site.json';
import { event } from '../lib/gtag';
export function StickyBanner() {
return (
<Flex as={Link}
href={siteConfig.url.youtube}
bg={'yellow.200'}
color='gray.900'
alignItems='center'
position='sticky'
top={0}
zIndex={999}
justifyContent='center'
py='8px'
_hover={{ textDecoration: 'none', bg: 'yellow.400' }}
target='_blank'
onClick={() => event({
category: 'Subscription',
action: 'Clicked the YouTube banner',
label: 'Sticky YouTube banner on Top'
})}
>
<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>
);
}

View File

@@ -0,0 +1,20 @@
import { Box, Button, Container, Heading, Link, Text } from '@chakra-ui/react';
import { event } from '../lib/gtag';
export function TeamsBanner() {
return (
<Box bg='teal.500' borderTopWidth={1} py={['45px', '45px', '70px']} textAlign='center'>
<Container maxW='container.sm'>
<Heading as='h4' color={'white'} fontSize={['25px', '25px', '35px']} mb={['10px', '10px', '20px']}>Roadmaps for Teams</Heading>
<Text lineHeight='26px' color={'white'} fontSize={['15px', '15px', '18px']} mb='20px'>We are working on a solution for teams. Help us shape the platform!</Text>
<Button onClick={() => {
event({
category: 'UpcomingFeatureClick',
action: `Teams Form Redirect`,
label: `Click Teams Footer Link`
});
}} target={'_blank'} as={Link} href='https://forms.gle/6X2matbCmjmvYGGt6' _hover={{textDecoration: 'none', bg: 'gray.300'}}>Take a Survey</Button>
</Container>
</Box>
);
}

View File

@@ -0,0 +1,69 @@
import { Badge, Box, Heading, Link, Text } from '@chakra-ui/react';
type VideoGridItemProps = {
href: string;
title: string;
subtitle: string;
date: string;
target?: string;
isNew?: 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 VideoGridItem(props: VideoGridItemProps) {
const { title, subtitle, date, isNew = false, colorIndex = 0, href, target } = props;
return (
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={ href } target={target || '_self'} shadow='xl' p='20px'
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1}>
<Text mb='7px' fontSize='12px' color='gray.400'>
{isNew && <Badge colorScheme={'green'} mr='10px'>New</Badge>}
{date}
</Text>
<Heading color='white' mb={'6px'} fontSize='20px' lineHeight={'28px'}>{title}</Heading>
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
</Box>
);
}

49
content/authors.json Normal file
View File

@@ -0,0 +1,49 @@
[
{
"username": "kamranahmedse",
"name": "Kamran Ahmed",
"twitter": "kamranahmedse",
"picture": "/authors/kamranahmedse.jpeg",
"bio": "Lead engineer at Tajawal. Lover of all things web and opensource. Created roadmap.sh to help the confused ones."
},
{
"username": "jesse",
"name": "Jesse Li",
"twitter": "__jesse_li",
"picture": "/authors/jesse.png",
"bio": "Software engineer."
},
{
"username": "dmytrobol",
"name": "Dmytro Bolkachov",
"twitter": "dmytrobol",
"picture": "/authors/dmytrobol.png",
"bio": "JavaScript Lad, Movie buff and coder interested in everything web related"
},
{
"username": "spekulatius",
"name": "Peter Thaleikis",
"twitter": "spekulatius1984",
"picture": "/authors/spekulatius.jpg",
"bio": "Developer building side-projects for fun, lover of the web and open source"
},
{
"username": "ebrahimbharmal007",
"name": "Ebrahim Bharmal",
"twitter": "BharmalEbrahim",
"picture": "/authors/ebrahimbharmal007.png",
"bio": "Love building projects using tools completely new to me. Python forever. Senior at University of Texas at Arlington (2021)"
},
{
"username": "lesovsky",
"name": "Alexey Lesovsky",
"bio": "Linux system administrator and PostgreSQL DBA at DataEgret.",
"picture": "/authors/lesovsky.jpeg"
},
{
"username": "danielgruesso",
"name": "Daniel Gruesso",
"bio": "Product manager working on blockchain and smart contracts developer tools",
"picture": "/authors/danielgruesso.jpg"
}
]

304
content/guides.json Normal file
View File

@@ -0,0 +1,304 @@
[
{
"id": "http-basic-authentication",
"title": "HTTP Basic Authentication",
"description": "Learn what is HTTP Basic Authentication and how to implement it in Node.js",
"isNew": true,
"type": "textual",
"authorUsername": "kamranahmedse",
"updatedAt": "2022-10-03T19:59:14.191Z",
"createdAt": "2022-10-03T19:59:14.191Z"
},
{
"id": "basics-of-authentication",
"title": "Basics of Authentication",
"description": "Learn the basics of Authentication and Authorization",
"isNew": true,
"type": "textual",
"authorUsername": "kamranahmedse",
"updatedAt": "2022-09-21T19:59:14.191Z",
"createdAt": "2022-09-21T19:59:14.191Z"
},
{
"id": "avoid-render-blocking-javascript-with-async-defer",
"title": "Async and Defer Script Loading",
"description": "Learn how to avoid render blocking JavaScript using async and defer scripts.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-09-10T19:59:14.191Z",
"createdAt": "2021-09-10T19:59:14.191Z"
},
{
"id": "what-are-web-vitals",
"title": "What are Web Vitals?",
"description": "Learn what are the core web vitals and how to measure them.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-09-05T19:59:14.191Z",
"createdAt": "2021-09-05T19:59:14.191Z"
},
{
"id": "what-is-sli-slo-sla",
"title": "SLIs, SLOs and SLAs",
"description": "Learn what are different indicators for performance identification of any service.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-08-31T19:59:14.191Z",
"createdAt": "2021-08-31T19:59:14.191Z"
},
{
"id": "ci-cd",
"title": "What is CI and CD?",
"description": "Learn the basics of CI/CD and how to implement that with GitHub Actions.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-07-09T19:59:14.191Z",
"createdAt": "2021-07-09T19:59:14.191Z"
},
{
"id": "sso",
"title": "SSO — Single Sign On",
"description": "Learn the basics of SAML and understand how does Single Sign On work.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-07-01T19:59:14.191Z",
"createdAt": "2021-07-01T19:59:14.191Z"
},
{
"id": "oauth",
"title": "OAuth — Open Authorization",
"description": "Learn and understand what is OAuth and how it works",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-06-28T19:59:14.191Z",
"createdAt": "2021-06-28T19:59:14.191Z"
},
{
"id": "jwt-authentication",
"title": "JWT Authentication",
"description": "Understand what is JWT authentication and how is it implemented",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-06-20T19:59:14.191Z",
"createdAt": "2021-06-20T19:59:14.191Z"
},
{
"id": "token-authentication",
"title": "Token Based Authentication",
"description": "Understand what is token based authentication and how it is implemented",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-06-02T20:59:14.191Z",
"createdAt": "2021-06-02T20:59:14.191Z"
},
{
"id": "session-authentication",
"title": "Session Based Authentication",
"description": "Understand what is session based authentication and how it is implemented",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-05-26T20:59:14.191Z",
"createdAt": "2021-05-26T20:59:14.191Z"
},
{
"id": "basic-authentication",
"title": "Basic Authentication",
"description": "Understand what is basic authentication and how it is implemented",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-05-19T20:59:14.191Z",
"createdAt": "2021-05-19T20:59:14.191Z"
},
{
"id": "character-encodings",
"title": "Character Encodings",
"description": "Covers the basics of character encodings and explains ASCII vs Unicode",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-05-14T20:59:14.191Z",
"createdAt": "2021-05-14T20:59:14.191Z"
},
{
"id": "unfamiliar-codebase",
"title": "Unfamiliar Codebase",
"description": "Tips on getting familiar with an unfamiliar codebase",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-05-04T20:59:14.191Z",
"createdAt": "2021-05-04T20:59:14.191Z"
},
{
"id": "why-build-it-and-they-will-come-wont-work-anymore",
"title": "Build it and they will come?",
"description": "Why “build it and they will come” alone wont work anymore",
"isNew": false,
"type": "textual",
"authorUsername": "spekulatius",
"updatedAt": "2021-05-04T12:59:14.191Z",
"createdAt": "2021-05-04T12:59:14.191Z"
},
{
"id": "dhcp-in-one-picture",
"title": "DHCP in One Picture",
"description": "Here is what happens when a new device joins the network.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-04-28T15:48:21.191Z",
"createdAt": "2021-04-28T15:48:21.191Z"
},
{
"id": "ssl-tls-https-ssh",
"title": "SSL vs TLS vs SSH",
"description": "Quick tidbit on the differences between SSL, TLS, HTTPS and SSH",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-04-22T15:48:21.191Z",
"createdAt": "2021-04-22T15:48:21.191Z"
},
{
"id": "asymptotic-notation",
"title": "Asymptotic Notation",
"description": "Learn the basics of measuring the time and space complexity of algorithms",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-04-03T15:48:21.191Z",
"createdAt": "2021-04-03T15:48:21.191Z"
},
{
"id": "big-o-notation",
"title": "Big-O Notation",
"description": "Easy to understand explanation of Big-O notation without any fancy terms",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-03-15T15:48:21.191Z",
"createdAt": "2021-03-15T15:48:21.191Z"
},
{
"id": "random-numbers",
"title": "Random Numbers: Are they?",
"description": "Learn how they are generated and why they may not be truly random.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-03-14T15:48:21.191Z",
"createdAt": "2021-03-14T15:48:21.191Z"
},
{
"id": "scaling-databases",
"title": "Scaling Databases",
"description": "Learn the ups and downs of different database scaling strategies",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-02-18T15:48:21.191Z",
"createdAt": "2021-02-18T15:48:21.191Z"
},
{
"id": "what-is-internet",
"title": "How does the internet work?",
"description": "Learn the basics of internet and everything involved with this short video series",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"updatedAt": "2021-02-29T15:48:21.191Z",
"createdAt": "2021-02-29T15:48:21.191Z"
},
{
"id": "torrent-client",
"title": "Building a BitTorrent Client",
"description": "Learn everything you need to know about BitTorrent by writing a client in Go",
"isNew": false,
"type": "textual",
"authorUsername": "jesse",
"updatedAt": "2021-01-17T15:48:21.191Z",
"createdAt": "2021-01-17T15:48:21.191Z",
"canonical": "https://blog.jse.li/posts/torrent/"
},
{
"id": "levels-of-seniority",
"title": "Levels of Seniority",
"description": "How to Step Up as a Junior, Mid Level or a Senior Developer?",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"updatedAt": "2020-12-03T12:13:00.860Z",
"createdAt": "2020-12-03T12:13:00.860Z"
},
{
"id": "design-patterns-for-humans",
"title": "Design Patterns for Humans",
"description": "A language agnostic, ultra-simplified explanation to design patterns",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"updatedAt": "2019-10-09T12:00:00.860Z",
"createdAt": "2019-01-23T17:00:00.860Z"
},
{
"id": "journey-to-http2",
"title": "Journey to HTTP/2",
"description": "The evolution of HTTP. How it all started and where we stand today",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"createdAt": "2018-12-04T12:00:00.860Z",
"updatedAt": "2018-12-04T12:00:00.860Z",
"isDraft": true
},
{
"id": "dns-in-one-picture",
"title": "DNS in One Picture",
"description": "Quick illustrative guide on how a website is found on the internet.",
"isNew": false,
"type": "visual",
"authorUsername": "kamranahmedse",
"updatedAt": "2018-12-04T12:00:00.860Z",
"createdAt": "2018-12-04T17:00:00.860Z"
},
{
"id": "http-caching",
"title": "HTTP Caching",
"description": "Everything you need to know about web caching",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"createdAt": "2018-11-29T17:00:00.860Z",
"updatedAt": "2018-11-29T17:00:00.860Z"
},
{
"id": "history-of-javascript",
"title": "Brief History of JavaScript",
"description": "How JavaScript was introduced and evolved over the years",
"isNew": false,
"type": "textual",
"authorUsername": "kamranahmedse",
"createdAt": "2017-10-28T17:00:00.860Z",
"updatedAt": "2017-10-28T17:00:00.860Z"
},
{
"id": "proxy-servers",
"title": "Proxy Servers",
"description": "How do proxy servers work and what are forward and reverse proxies?",
"isNew": false,
"type": "textual",
"authorUsername": "ebrahimbharmal007",
"createdAt": "2017-10-24T17:00:00.860Z",
"updatedAt": "2017-10-24T17:00:00.860Z"
}
]

View File

@@ -0,0 +1,15 @@
export const guideMeta = {
"title": "WebStorm — Project History",
"description": "Learn how to peek through the history of any git repository to learn how it grew.",
"url": "/guides/project-history",
"fileName": "project-history",
"featured": true,
"author": "kamranahmedse",
"updatedAt": "2020-07-16T19:59:14.191Z",
"createdAt": "2020-07-16T19:59:14.191Z"
};
Asymptotic notation is the standard way of measuring the time and space that an algorithm will consume as the input grows. In one of my last guides, I covered "Big-O notation" and a lot of you asked for a similar one for Asymptotic notation. You can find the [previous guide here](/guides/big-o-notation).
[![](/guides/asymptotic-notation.png)](/guides/asymptotic-notation.png)

View File

@@ -0,0 +1,2 @@
[![](/guides/avoid-render-blocking-javascript-with-async-defer.png)](/guides/avoid-render-blocking-javascript-with-async-defer.png)

View File

@@ -0,0 +1,2 @@
[![](/guides/basic-authentication.png)](/guides/basic-authentication.png)

View File

@@ -0,0 +1,83 @@
Our last video series was about data structures. We looked at the most common data structures, their use cases, pros and cons, and the different operations you could perform on each data structure.
Today, we are kicking off a similar series for Authentication strategies where we will discuss everything you need to know about authentication and authentication strategies.
In this guide today will be talking about what authentication is, and we will cover some terminology that will help us later in the series. You can watch the video below or continue reading this guide.
<iframe src="https://www.youtube.com/embed/Mcyt9SrZT6g" title="Basics of Authentication" />
## What is Authentication?
Authentication is the process of verifying someone's identity. A real-world example of that would be when you board a plane, the airline worker checks your passport to verify your identity, so the airport worker authenticates you.
If we talk about computers, when you log in to any website, you usually authenticate yourself by entering your username and password, which is then checked by the website to ensure that you are who you claim to be. There are two things you should keep in mind:
- Authentication is not only for the persons
- And username and password are not the only way to authenticate.
Some other examples are:
- When you open a website in the browser. If the website uses HTTP, TLS is used to authenticate the server and avoid the fake loading of websites.
- There might be server-to-server communication on the website. The server may need to authenticate the incoming request to avoid malicious usage.
## How does Authentication Work?
On a high level, we have the following factors used for authentication.
- **Username and Password**
- **Security Codes, Pin Codes, or Security Questions** — An example would be the pin code you enter at an ATM to withdraw cash.
- **Hard Tokens and Soft Tokens** — Hard tokens are the special hardware devices that you attach to your device to authenticate yourself. Soft tokens, unlike hard tokens, don't have any authentication-specific device; we must verify the possession of a device that was used to set up the identity. For example, you may receive an OTP to log in to your account on a website.
- **Biometric Authentication** — In biometric authentication, we authenticate using biometrics such as iris, facial, or voice recognition.
We can categorize the factors above into three different types.
- Username / Password and Security codes rely on the person's knowledge: we can group them under the **Knowledge Factor**.
- In hard and soft tokens, we authenticate by checking the possession of hardware, so this would be a **Possession Factor**.
- And in biometrics, we test the person's inherent qualities, i.e., iris, face, or voice, so this would be a **Qualities** factor.
This brings us to our next topic: Multi-factor Authentication and Two-Factor Authentication.
## Multifactor Authentication
Multifactor authentication is the type of authentication in which we rely on more than one factor to authenticate a user.
For example, if we pick up username/password from the **knowledge factor**. And we pick soft tokens from the **possession factor**, and we say that for a user to authenticate, they must enter their credentials and an OTP, which will be sent to their mobile phone, so this would be an example of multifactor authentication.
In multifactor authentication, since we rely on more than one factor, this way of authentication is much more secure than single-factor authentication.
One important thing to note here is that the factors you pick for authentication, they must differ. So, for example, if we pick up a username/password and security question or security codes, it is still not true multifactor authentication because we still rely on the knowledge factor. The factors have to be different from each other.
### Two-Factor Authentication
Two-factor authentication is similar to multifactor authentication. The only difference is that there are precisely two factors in 2FA. In MFA, we can have 2, 3, 4, or any authentication factors; 2FA has exactly two factors. We can say that 2FA is always MFA, because there are more than one factors. MFA is not always 2FA because there may be more than two factors involved.
Next we have the difference between authentication and authorization. This comes up a lot in the interviews, and beginners often confuse them.
### What is Authentication
Authentication is the process of verifying the identity. For example, when you enter your credentials at a login screen, the application here identifies you through your credentials. So this is what the authentication is, the process of verifying the identity.
In case of an authentication failure, for example, if you enter an invalid username and password, the HTTP response code is "Unauthorized" 401.
### What is Authorization
Authorization is the process of checking permission. Once the user has logged in, i.e., the user has been authenticated, the process of reviewing the permission to see if the user can perform the relevant operation or not is called authorization.
And in case of authorization failure, i.e., if the user tries to perform an operation they are not allowed to perform, the HTTP response code is forbidden 403.
## Authentication Strategies
Given below is the list of common authentication strategies:
- Basics of Authentication
- Session Based Authentication
- Token-Based Authentication
- JWT Authentication
- OAuth - Open Authorization
- Single Sign On (SSO)
In this series of illustrated videos and textual guides, we will be going through each of the strategies discussing what they are, how they are implemented, the pros and cons and so on.
So stay tuned, and I will see you in the next one.

View File

@@ -0,0 +1,4 @@
Big-O notation is the mathematical notation that helps analyse the algorithms to get an idea about how they might perform as the input grows. The image below explains Big-O in a simple way without using any fancy terminology.
[![](/guides/big-o-notation.png)](/guides/big-o-notation.png)

View File

@@ -0,0 +1,2 @@
[![](/guides/character-encodings.png)](/guides/character-encodings.png)

4
content/guides/ci-cd.md Normal file
View File

@@ -0,0 +1,4 @@
The image below details the differences between the continuous integration and continuous delivery. Also, here is the [accompanying video on implementing that with GitHub actions](https://www.youtube.com/watch?v=nyKZTKQS_EQ).
[![](/guides/ci-cd.png)](/guides/ci-cd.png)

View File

@@ -1,25 +1,3 @@
---
title: "Design Patterns for Humans"
description: "A language agnostic, ultra-simplified explanation to design patterns"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "Design Patterns for Humans - roadmap.sh"
description: "A language agnostic, ultra-simplified explanation to design patterns"
isNew: false
type: "textual"
date: 2019-01-23
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
Design patterns are solutions to recurring problems; **guidelines on how to tackle certain problems**. They are not classes, packages or libraries that you can plug into your application and wait for the magic to happen. These are, rather, guidelines on how to tackle certain problems in certain situations.
> Design patterns are solutions to recurring problems; guidelines on how to tackle certain problems

View File

@@ -0,0 +1,2 @@
[![](/guides/dhcp.png)](/guides/dhcp.png)

View File

@@ -0,0 +1,5 @@
DNS or Domain Name System is one of the fundamental blocks of the internet. As a developer, you should have at-least the basic understanding of how it works. This article is a brief introduction to what is DNS and how it works.
DNS at its simplest is like a phonebook on your mobile phone. Whenever you have to call one of your contacts, you can either dial their number from your memory or use their name which will then be used by your mobile phone to search their number in your phone book to call them. Every time you make a new friend, or your existing friend gets a mobile phone, you have to memorize their phone number or save it in your phonebook to be able to call them later on. DNS or Domain Name System, in a similar fashion, is a mechanism that allows you to browse websites on the internet. Just like your mobile phone does not know how to call without knowing the phone number, your browser does not know how to open a website just by the domain name; it needs to know the IP Address for the website to open. You can either type the IP Address to open, or provide the domain name and press enter which will then be used by your browser to find the IP address by going through several hoops. The picture below is the illustration of how your browser finds a website on the internet.
[![](https://i.imgur.com/z9rwm5A.png)](https://i.imgur.com/z9rwm5A.png)

View File

@@ -1,25 +1,3 @@
---
title: "Brief History of JavaScript"
description: "How JavaScript was introduced and evolved over the years"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "Brief History of JavaScript - roadmap.sh"
description: "How JavaScript was introduced and evolved over the years"
isNew: false
type: "textual"
date: 2017-10-28
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
Around 10 years ago, Jeff Atwood (the founder of stackoverflow) made a case that JavaScript is going to be the future and he coined the “Atwood Law” which states that *Any application that can be written in JavaScript will eventually be written in JavaScript*. Fast-forward to today, 10 years later, if you look at it it rings truer than ever. JavaScript is continuing to gain more and more adoption.
### JavaScript is announced

View File

@@ -1,30 +1,8 @@
---
title: "HTTP Basic Authentication"
description: "Learn what is HTTP Basic Authentication and how to implement it in Node.js"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "HTTP Basic Authentication - roadmap.sh"
description: "Learn what is HTTP Basic Authentication and how to implement it in Node.js"
isNew: true
type: "textual"
date: 2022-10-03
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
Our last guide was about the [basics of authentication](/guides/basics-of-authentication), where we discussed authentication, authorization, types of authentication, authentication factors, authentication strategies, and so on.
In this guide today, we will be learning about basic authentication, and we will see how we can implement Basic Authentication in Node.js. We have a [visual guide on the basic authentication](/guides/basic-authentication) and an illustrative video, watch the video below or continue reading:
<iframe class="w-full aspect-video mb-5" src="https://www.youtube.com/embed/mwccHwUn7Gc" title="HTTP Basic Authentication"></iframe>
<iframe src="https://www.youtube.com/embed/mwccHwUn7Gc" title="HTTP Basic Authentication" />
## What is Basic Authentication?
Given the name "Basic Authentication", you should not confuse Basic Authentication with the standard username and password authentication. Basic authentication is a part of the HTTP specification, and the details can be [found in the RFC7617](https://www.rfc-editor.org/rfc/rfc7617.html).
@@ -40,7 +18,7 @@ Now that we know what basic authentication is, the question is, how does it work
### Step 1
When the browser first requests the server, the server tries to check the availability of the `Authorization` header in the request. Because it is the first request, no `Authorization` header is found in the request. So the server responds with the `401 Unauthorized` response code and also sends the `WWW-Authenticate` header with the value set to `Basic`, which tells the browser that it needs to trigger the basic authentication flow.
```
```text
401 Unauthorized
WWW-Authenticate: Basic realm='user_pages'
```

View File

@@ -1,25 +1,3 @@
---
title: "HTTP Caching"
description: "Everything you need to know about web caching"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "HTTP Caching - roadmap.sh"
description: "Everything you need to know about web caching"
isNew: false
type: "textual"
date: 2018-11-29
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
As users, we easily get frustrated by the buffering of videos, the images that take seconds to load, and pages that got stuck because the content is being loaded. Loading the resources from some cache is much faster than fetching the same from the originating server. It reduces latency, speeds up the loading of resources, decreases the load on the server, cuts down the bandwidth costs etc.
### Introduction

View File

@@ -1,25 +1,3 @@
---
title: "Journey to HTTP/2"
description: "The evolution of HTTP. How it all started and where we stand today"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "Journey to HTTP/2 - roadmap.sh"
description: "The evolution of HTTP. How it all started and where we stand today"
isNew: false
type: "textual"
date: 2018-12-04
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
HTTP is the protocol that every web developer should know as it powers the whole web and knowing it is definitely going to help you develop better applications. In this guide, I am going to be discussing what HTTP is, how it came to be, where it is today and how did we get here.
### What is HTTP?
@@ -171,7 +149,7 @@ By now, you must be convinced that why we needed another revision of the HTTP pr
##### Frames and Streams
HTTP messages are now composed of one or more frames. There is a `HEADERS` frame for the meta data and `DATA` frame for the payload and there exist several other types of frames (`HEADERS`, `DATA`, `RST_STREAM`, `SETTINGS`, `PRIORITY` etc) that you can check through [the `HTTP/2` specs](https:/http2.github.iohttp2-spec/#FrameTypes).
HTTP messages are now composed of one or more frames. There is a `HEADERS` frame for the meta data and `DATA` frame for the payload and there exist several other types of frames (`HEADERS`, `DATA`, `RST_STREAM`, `SETTINGS`, `PRIORITY` etc) that you can check through [the `HTTP/2` specs](https://http2.github.io/http2-spec/#FrameTypes).
Every `HTTP/2` request and response is given a unique stream ID and it is divided into frames. Frames are nothing but binary pieces of data. A collection of frames is called a Stream. Each frame has a stream id that identifies the stream to which it belongs and each frame has a common header. Also, apart from stream ID being unique, it is worth mentioning that, any request initiated by client uses odd numbers and the response from server has even numbers stream IDs.
@@ -212,6 +190,6 @@ There was extensive discussion on whether security (through `TLS`) should be mad
`HTTP/2` is here and it has already [surpassed SPDY in adaption](http://caniuse.com/#search=http2) which is gradually increasing. `HTTP/2` has alot to offer in terms of performance gain and it is about time we should start using it.
For anyone interested in further details here is the [link to specs](https:/http2.github.iohttp2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](http://www.http2demo.io/).
For anyone interested in further details here is the [link to specs](https://http2.github.io/http2-spec) and a link [demonstrating the performance benefits of `HTTP/2`](http://www.http2demo.io/).
And that about wraps it up. Until next time! stay tuned.

View File

@@ -0,0 +1,2 @@
[![](/guides/jwt-authentication.png)](/guides/jwt-authentication.png)

View File

@@ -1,25 +1,3 @@
---
title: "Levels of Seniority"
description: "How to Step Up as a Junior, Mid Level or a Senior Developer?"
author:
name: "Kamran Ahmed"
url: "https://twitter.com/kamranahmedse"
imageUrl: "/authors/kamranahmedse.jpeg"
seo:
title: "Levels of Seniority - roadmap.sh"
description: "How to Step Up as a Junior, Mid Level or a Senior Developer?"
isNew: false
type: "textual"
date: 2020-12-03
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
I have been working on redoing the [roadmaps](https://roadmap.sh) splitting the skillset based on the seniority levels to make them easier to follow and not scare the new developers away. Since the roadmaps are going to be just about the technical knowledge, I thought it would be a good idea to reiterate and have an article on what I think of different seniority roles.
I have seen many organizations decide the seniority of developers by giving more significance to the years of experience than they should. I have seen developers labeled "Junior" doing the work of Senior Developers and I have seen "Lead" developers who weren't even qualified to be called "Senior". The seniority of a developer cannot just be decided by their age, years of experience or technical knowledge that they have got. There are other factors in play here -- their perception of work, how they interact with their peers and how they approach problems. We discuss these three key factors in detail for each of the seniority levels below.

2
content/guides/oauth.md Normal file
View File

@@ -0,0 +1,2 @@
[![](/guides/oauth.png)](/guides/oauth.png)

View File

@@ -0,0 +1,5 @@
One of my favorite pastimes is going through the history of my favorite projects to learn how they grew over time or how certain features were implemented.
The image below describes how I do that in WebStorm.
[![](/guides/project-history.png)](/guides/project-history.png)

View File

@@ -0,0 +1,47 @@
The Internet has connected people across the world using social media and audio/video calling features along with providing an overabundance of knowledge and tools. All this comes with an inherent danger of security and privacy breaches. In this guide, we will talk about **proxies** that play a vital role in mitigating these risks. We will cover the following topics in this guide:
- [Proxy Server](#proxy-server)
- [Forward Proxy Server](#forward-proxy-server)
- [Reverse Proxy Server](#reverse-proxy-server)
- [Summary](#summary)
## Proxy Server
***Every web request which is sent from the client to a web server goes through some type of proxy server.*** A proxy server acts as a gateway between client *(you)* and the internet and separates end-users from the websites you browse. It replaces the source IP address of the web request with the proxy server's IP address and then forwards it to the web server. The web server is unaware of the client, it only sees the proxy server.
![Proxy Server Description](/guides/proxy/proxy-example.png)
> NOTE: This is not an accurate description but rather just an illustration.
Proxy servers serve as a single point of control making it easier to enforce security policies. It also provides caching mechanism which stores the requested web pages on the proxy server to improve performance. If the requested web-page is available in cache memory then instead of forwarding the request to the web-server it will send the cached webpage back to the client. This **saves big companies thousands of dollars** by reducing the load on their servers as their website is visited by millions of users every day.
## Forward Proxy Server
A forward proxy is generally implemented on the client side and **sits in front of multiple clients** or client sources. Forward proxy servers are mainly used by companies to **manage internet usage** of their employees and **restrict content**. It is also used as a **firewall** to secure the company's network by blocking any request which would pose threat to the companies's network. Proxy servers are also used to **bypass geo-restriction** and browse content that might be blocked in the user's country. It enables users to **browse anonymously**, as the proxy server masks their details from the website's servers.
![Forward Proxy Description](/guides/proxy/forward-proxy.png)
> NOTE: This is not an accurate description but rather just an illustration
## Reverse Proxy Server
Reverse proxy servers are implemented on the **server side** instead of the client side. It **sits in front of multiple webservers** and manages the incoming requests by forwarding them to the web servers. It provides anonymity for the **back-end web servers and not the client**. Reverse proxy servers are generally used to perform tasks such as **authentication, content caching, and encryption/decryption** on behalf of the web server. These tasks would **hog CPU cycles** on the web server and degrade the performance of the website by introducing a high amount of delay in loading the webpage. Reverse proxies are also used as **load balancers** to distribute the incoming traffic efficiently among the web servers but it is **not optimized** for this task. In essence, a reverse proxy server is a gateway to a web-server or group of web-servers.
![Reverse Proxy Description](/guides/proxy/reverse-proxy.png)
> NOTE: This is not an accurate description but rather just an illustration. Red lines represent server's response and black lines represent initial request from client(s).
## Summary
A proxy server acts as a gateway between client *(you)* and the internet and separates end-users from the websites you browse. ***The position of the proxy server on the network determines whether it is a forward or a reverse proxy server***. A Forward proxy is implemented on the client side and **sits in front of multiple clients** or client sources and forwards requests to the web server. Reverse proxy servers are implemented on the **server side** it **sits in front of multiple webservers** and manages the incoming requests by forwarding them to the web servers.
If all this was too much to take in, I have a simple analogy for you.
At a restaurant, the waiter/waitress takes your order and gives it to the kitchen head chef. The head chef then calls out the order and assigns tasks to everyone in the kitchen.
In this analogy:
* You are the client
* Your order is the web request
* Waiter/Waitress is your forward proxy server
* Kitchen head chef is the reverse proxy server
* Other chefs working in the kitchen are the web servers
With that said our guide comes to an end. Thank you for reading and feel free to submit any updates to the guide using the links below.

View File

@@ -0,0 +1,4 @@
Random numbers are everywhere from computer games to lottery systems, graphics software, statistical sampling, computer simulation and cryptography. Graphic below is a quick explanation to how the random numbers are generated and why they may not be truly random.
[![](/guides/random-numbers.png)](/guides/random-numbers.png)

View File

@@ -0,0 +1,4 @@
The chart below aims to give you a really basic understanding of how the capability of a DBMS is increased to handle a growing amount of load.
[![](/guides/scaling-databases.svg)](/guides/scaling-databases.svg)

View File

@@ -0,0 +1,2 @@
[![](/guides/session-authentication.png)](/guides/session-authentication.png)

View File

@@ -0,0 +1,2 @@
[![](/guides/ssl-tls-https-ssh.png)](/guides/ssl-tls-https-ssh.png)

2
content/guides/sso.md Normal file
View File

@@ -0,0 +1,2 @@
[![](/guides/sso.png)](/guides/sso.png)

View File

@@ -0,0 +1,8 @@
# Threads and Concurrency
A thread is an execution context in which the instructions to the CPU can be scheduled and executed independently of the parent process. Concurrency is the concept of multiple threads in a shared memory space being computed simultaneously (or intermittently executed in succession to provide that illusion). Concurrency allows multiple processes to execute at once and can apply to programming languages as well as operating systems.
<ResourceGroupTitle>Free Content</ResourceGroupTitle>
<BadgeLink badgeText='Watch' href='https://www.youtube.com/watch?v=olYdb0DdGtM'>Threading Tutorial #1 - Concurrency, Threading and Parallelism</BadgeLink>
<BadgeLink colorScheme='yellow' badgeText='Read' href='https://medium.com/@akhandmishra/operating-system-threads-and-concurrency-aec2036b90f8'>Operating System: Threads and Concurrency</BadgeLink>
<BadgeLink colorScheme='yellow' badgeText='Read' href='https://web.mit.edu/6.005/www/fa14/classes/17-concurrency/'>Reading 17: Concurrency</BadgeLink>

View File

@@ -0,0 +1,2 @@
[![](/guides/token-authentication.png)](/guides/token-authentication.png)

View File

@@ -1,25 +1,3 @@
---
title: "Building a BitTorrent Client"
description: "Learn everything you need to know about BitTorrent by writing a client in Go"
author:
name: "Jesse Li"
url: "https://twitter.com/__jesse_li"
imageUrl: "/authors/jesse.png"
seo:
title: "Building a BitTorrent Client - roadmap.sh"
description: "Learn everything you need to know about BitTorrent by writing a client in Go"
isNew: false
type: "textual"
date: 2021-01-17
sitemap:
priority: 0.7
changefreq: "weekly"
tags:
- "guide"
- "textual-guide"
- "guide-sitemap"
---
BitTorrent is a protocol for downloading and distributing files across the Internet. In contrast with the traditional client/server relationship, in which downloaders connect to a central server (for example: watching a movie on Netflix, or loading the web page you're reading now), participants in the BitTorrent network, called **peers**, download pieces of files from *each other*—this is what makes it a **peer-to-peer** protocol. In this article we will investigate how this works, and build our own client that can find peers and exchange data between them.
![diagram showing the difference between client/server (all clients connecting to one server) and peer-to-peer (peers connecting to each other) relationships](/guides/torrent-client/client-server-p2p.png)

View File

@@ -0,0 +1,2 @@
[![](/guides/unfamiliar-codebase.png)](/guides/unfamiliar-codebase.png)

View File

@@ -0,0 +1,11 @@
> **Roadmap is not ready yet**. Please check back later or [subscribe to get notified](/signup).
While we prepare the roadmap, follow this simple advice to learn anything
> Just **pick a project and start working on it**, you will learn all that you need along the way.
**&rarr;** &nbsp; [All Roadmaps](/roadmaps) &nbsp;&bull;&nbsp; [Programming guides](/guides) &nbsp;&bull;&nbsp; [Subscribe](/signup)

Some files were not shown because too many files have changed in this diff Show More