Compare commits
3 Commits
fix/conten
...
update/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac65ea1e1 | ||
|
|
234b562857 | ||
|
|
ef67abc210 |
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"devToolbar": {
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1724925726721
|
||||
}
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="astro/client" />
|
||||
@@ -1,3 +0,0 @@
|
||||
PUBLIC_API_URL=https://api.roadmap.sh
|
||||
PUBLIC_AVATAR_BASE_URL=https://dodrc8eu8m09s.cloudfront.net/avatars
|
||||
PUBLIC_EDITOR_APP_URL=https://draw.roadmap.sh
|
||||
18
.eslintrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": [
|
||||
"next",
|
||||
"next/core-web-vitals",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@next/next/no-img-element": [
|
||||
"off"
|
||||
],
|
||||
"react/display-name": [
|
||||
"off"
|
||||
],
|
||||
"react/jsx-no-target-blank": [
|
||||
"off"
|
||||
]
|
||||
}
|
||||
}
|
||||
25
.github/ISSUE_TEMPLATE/01-suggest-changes.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: "✍️ Missing or Deprecated Roadmap Topics"
|
||||
description: Help us improve the roadmaps by suggesting changes
|
||||
labels: [topic-change]
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to help us improve the roadmaps with your suggestions.
|
||||
- type: input
|
||||
id: url
|
||||
attributes:
|
||||
label: Roadmap URL
|
||||
description: Please provide the URL of the roadmap you are suggesting changes to.
|
||||
placeholder: https://roadmap.sh
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: roadmap-suggestions
|
||||
attributes:
|
||||
label: Suggestions
|
||||
description: What changes would you like to suggest?
|
||||
placeholder: Enter your suggestions here.
|
||||
validations:
|
||||
required: true
|
||||
42
.github/ISSUE_TEMPLATE/02-bug-report.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: "🐛 Bug Report"
|
||||
description: Report an issue or possible bug
|
||||
labels: [bug]
|
||||
assignees: []
|
||||
body:
|
||||
- type: input
|
||||
id: url
|
||||
attributes:
|
||||
label: What is the URL where the issue is happening
|
||||
placeholder: https://roadmap.sh
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Other
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Output from browser console (if any)
|
||||
description: Please copy and paste any relevant log output.
|
||||
- type: checkboxes
|
||||
id: will-pr
|
||||
attributes:
|
||||
label: Participation
|
||||
options:
|
||||
- label: I am willing to submit a pull request for this issue.
|
||||
required: false
|
||||
12
.github/ISSUE_TEMPLATE/03-feature-suggestion.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: "✨ Feature Suggestion"
|
||||
description: Is there a feature you'd like to see on Roadmap.sh? Let us know!
|
||||
labels: [feature request]
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Feature Description
|
||||
description: Please provide a detailed description of the feature you are suggesting and how it would help you/others.
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,25 +0,0 @@
|
||||
name: "🙏 Submit a Roadmap"
|
||||
description: Help us launch a new roadmap with your expertise.
|
||||
labels: [roadmap contribution]
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to submit a roadmap! Please fill out the information below and we'll get back to you as soon as we can.
|
||||
- type: input
|
||||
id: roadmap-title
|
||||
attributes:
|
||||
label: What is the title of the roadmap you are submitting?
|
||||
placeholder: e.g. Roadmap to learn Data Science
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: roadmap-description
|
||||
attributes:
|
||||
label: Roadmap Link
|
||||
description: Please create the roadmap [using our roadmap editor](https://twitter.com/kamrify/status/1708293162693767426) and submit the roadmap link.
|
||||
placeholder: |
|
||||
https://roadmap.sh/xyz
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,35 +0,0 @@
|
||||
name: "🙏 Submit a Project Idea"
|
||||
description: Help us add project ideas to roadmaps.
|
||||
labels: [project contribution]
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to submit a project idea! Please fill out the information below and we'll get back to you as soon as we can.
|
||||
- type: input
|
||||
id: roadmap-title
|
||||
attributes:
|
||||
label: What Roadmap is this project for?
|
||||
placeholder: e.g. Backend Roadmap
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: project-difficulty
|
||||
attributes:
|
||||
label: Project Difficulty
|
||||
options:
|
||||
- Beginner
|
||||
- Intermediate
|
||||
- Advanced
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: roadmap-description
|
||||
attributes:
|
||||
label: Add Project Details
|
||||
description: Please write a detailed description of the project in 3rd person e.g. "You are required to build a..."
|
||||
placeholder: |
|
||||
e.g. You are required to build a RESTful API...
|
||||
validations:
|
||||
required: true
|
||||
12
.github/ISSUE_TEMPLATE/05-something-else.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: "🤷♂️ Something else"
|
||||
description: If none of the above templates fit your needs, please use this template to submit your issue.
|
||||
labels: []
|
||||
assignees: []
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue-description
|
||||
attributes:
|
||||
label: Detailed Description
|
||||
description: Please provide a detailed description of the issue.
|
||||
validations:
|
||||
required: true
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ✋ Roadmap Request
|
||||
url: https://roadmap.sh/discord
|
||||
about: Please do not open issues with roadmap requests, hop onto the discord server for that.
|
||||
- name: 📝 Typo or Grammatical Mistake
|
||||
url: https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data
|
||||
about: Please submit a pull request instead of reporting it as an issue.
|
||||
- name: 💬 Chat on Discord
|
||||
url: https://roadmap.sh/discord
|
||||
about: Join the community on our Discord server.
|
||||
- name: 🤝 Guidance
|
||||
url: https://roadmap.sh/discord
|
||||
about: Join the community in our Discord server.
|
||||
BIN
.github/images/banner.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
1
.github/sponsors/doppler-logo.svg
vendored
Normal 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
@@ -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 |
11
.github/sponsors/workos-logo-white-bg.svg
vendored
Normal 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 |
21
.github/workflows/aws-costs.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Sends Daily AWS Costs to Slack
|
||||
on:
|
||||
# Allow manual Run
|
||||
workflow_dispatch:
|
||||
# Run at 7:00 UTC every day
|
||||
schedule:
|
||||
- cron: "0 7 * * *"
|
||||
jobs:
|
||||
aws_costs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get Costs
|
||||
env:
|
||||
AWS_KEY: ${{ secrets.COST_AWS_ACCESS_KEY }}
|
||||
AWS_SECRET: ${{ secrets.COST_AWS_SECRET_KEY }}
|
||||
AWS_REGION: ${{ secrets.COST_AWS_REGION }}
|
||||
SLACK_CHANNEL: ${{ secrets.SLACK_COST_CHANNEL }}
|
||||
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
|
||||
run: |
|
||||
npm install -g aws-cost-cli
|
||||
aws-cost -k $AWS_KEY -s $AWS_SECRET -r $AWS_REGION -S $SLACK_TOKEN -C $SLACK_CHANNEL
|
||||
50
.github/workflows/close-feedback-pr.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Close PRs with Feedback
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
jobs:
|
||||
close-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close PR if it has label "feedback left" and no changes in 7 days
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: pullRequests } = await github.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
base: 'master',
|
||||
});
|
||||
|
||||
for (const pullRequest of pullRequests) {
|
||||
const { data: labels } = await github.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
});
|
||||
|
||||
const feedbackLabel = labels.find((label) => label.name === 'feedback left');
|
||||
if (feedbackLabel) {
|
||||
const lastUpdated = new Date(pullRequest.updated_at);
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
if (lastUpdated < sevenDaysAgo) {
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
body: 'Closing this PR because there has been no activity for the past 7 days. Feel free to reopen if you have any feedback.',
|
||||
});
|
||||
await github.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pullRequest.number,
|
||||
state: 'closed',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.github/workflows/cloudfront-api-cache.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Clears API Cloudfront Cache
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
aws_costs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clear Cloudfront Caching
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
|
||||
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront-api", "is_verbose": false } }'
|
||||
16
.github/workflows/cloudfront-fe-cache.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Clears Frontend Cloudfront Cache
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
aws_costs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clear Cloudfront Caching
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
|
||||
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'
|
||||
33
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Deployment to GH Pages
|
||||
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
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
npm install
|
||||
- name: Generate meta and build
|
||||
run: |
|
||||
npm run meta
|
||||
npm run build
|
||||
- name: Deploy to GH Pages
|
||||
run: |
|
||||
git config user.email "kamranahmed.se@gmail.com"
|
||||
git config user.name "Kamran Ahmed"
|
||||
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
||||
npm run deploy
|
||||
75
.github/workflows/deployment.yml
vendored
@@ -1,75 +0,0 @@
|
||||
name: Deploy to EC2
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- uses: pnpm/action-setup@v4.0.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
# -------------------
|
||||
# Setup configuration
|
||||
# -------------------
|
||||
- name: Prepare configuration files
|
||||
run: |
|
||||
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/infra-config.git configuration --depth 1
|
||||
- name: Copy configuration files
|
||||
run: |
|
||||
cp configuration/dist/github/developer-roadmap.env .env
|
||||
|
||||
# -----------------
|
||||
# Prepare the Build
|
||||
# -----------------
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Generate Production Build
|
||||
run: |
|
||||
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/web-draw.git .temp/web-draw --depth 1
|
||||
npm run generate-renderer
|
||||
npm run compress:images
|
||||
npm run build
|
||||
|
||||
# --------------------
|
||||
# Deploy to EC2
|
||||
# --------------------
|
||||
- uses: webfactory/ssh-agent@v0.7.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.EC2_PRIVATE_KEY }}
|
||||
- name: Deploy Application to EC2
|
||||
run: |
|
||||
rsync -apvz --delete --no-times --exclude "configuration" -e "ssh -o StrictHostKeyChecking=no" -p ./ ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/var/www/roadmap.sh/
|
||||
- name: Restart PM2
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.EC2_HOST }}
|
||||
username: ${{ secrets.EC2_USERNAME }}
|
||||
key: ${{ secrets.EC2_PRIVATE_KEY }}
|
||||
script: |
|
||||
cd /var/www/roadmap.sh
|
||||
sudo pm2 restart web-roadmap
|
||||
|
||||
# ----------------------
|
||||
# Clear cloudfront cache
|
||||
# ----------------------
|
||||
- name: Clear Cloudfront Caching
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
|
||||
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'
|
||||
40
.github/workflows/label-issue.yml
vendored
@@ -1,40 +0,0 @@
|
||||
name: Label Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [ opened, edited ]
|
||||
|
||||
jobs:
|
||||
label-topic-change-issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add Labels To Issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const roadmapUrl = issue.body.match(/https?:\/\/roadmap.sh\/[^ ]+/);
|
||||
|
||||
// if the issue is labeled as a topic-change, add the roadmap slug as a label
|
||||
if (issue.labels.some(label => label.name === 'topic-change')) {
|
||||
if (roadmapUrl) {
|
||||
const roadmapSlug = new URL(roadmapUrl[0]).pathname.replace(/\//, '');
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [roadmapSlug]
|
||||
});
|
||||
}
|
||||
|
||||
// Close the issue if it has no roadmap URL
|
||||
if (!roadmapUrl) {
|
||||
github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Refresh Roadmap Content JSON
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
refresh-content:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm@v9
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js Version 20 (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install Dependencies and Generate Content JSON
|
||||
run: |
|
||||
pnpm install
|
||||
npm run generate:roadmap-content-json
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
delete-branch: false
|
||||
branch: "chore/update-content-json"
|
||||
base: "master"
|
||||
labels: |
|
||||
dependencies
|
||||
automated pr
|
||||
reviewers: kamranahmedse
|
||||
commit-message: "chore: update roadmap content json"
|
||||
title: "Updated Roadmap Content JSON - Automated"
|
||||
body: |
|
||||
## Updated Roadmap Content JSON
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This PR Updates the Roadmap Content JSON files stored in the `public` directory.
|
||||
>
|
||||
> Commit: ${{ github.sha }}
|
||||
> Workflow Path: ${{ github.workflow_ref }}
|
||||
|
||||
**Please Review the Changes and Merge the PR if everything is fine.**
|
||||
51
.github/workflows/upgrade-dependencies.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Upgrade Dependencies
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
upgrade-deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js Version 20 (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm@v9
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install & Upgrade Dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
npm run upgrade
|
||||
pnpm install --lockfile-only
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
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 - Automated"
|
||||
body: |
|
||||
## Updated all Dependencies to Latest Versions.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This PR Upgrades the Dependencies to the Latest Their Versions.
|
||||
>
|
||||
> Commit: ${{ github.sha }}
|
||||
> Workflow Path: ${{ github.workflow_ref }}
|
||||
|
||||
**Please Review the Changes and Merge the PR if everything is fine.**
|
||||
54
.gitignore
vendored
@@ -1,35 +1,37 @@
|
||||
.idea
|
||||
.temp
|
||||
|
||||
# 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
|
||||
yarn.lock
|
||||
|
||||
scripts/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
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
tests-examples
|
||||
*.csv
|
||||
|
||||
/editor/*
|
||||
!/editor/readonly-editor.tsx
|
||||
!/editor/renderer/renderer.ts
|
||||
!/editor/renderer/index.tsx
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
app-dist
|
||||
dist
|
||||
.idea
|
||||
.github
|
||||
public
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
overrides: [
|
||||
{
|
||||
files: '*.astro',
|
||||
options: {
|
||||
parser: 'astro',
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('prettier-plugin-astro'),
|
||||
'prettier-plugin-tailwindcss',
|
||||
],
|
||||
};
|
||||
4
.vscode/extensions.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
.vscode/launch.json
vendored
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"prettier.documentSelectors": ["**/*.astro"],
|
||||
"[astro]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// https://astro.build/config
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import node from '@astrojs/node';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://roadmap.sh/',
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
theme: 'dracula',
|
||||
},
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypeExternalLinks,
|
||||
{
|
||||
target: '_blank',
|
||||
rel: function (element) {
|
||||
const href = element.properties.href;
|
||||
const whiteListedStarts = [
|
||||
'/',
|
||||
'#',
|
||||
'mailto:',
|
||||
'https://github.com/kamranahmedse',
|
||||
'https://thenewstack.io',
|
||||
'https://kamranahmed.info',
|
||||
'https://roadmap.sh',
|
||||
];
|
||||
if (whiteListedStarts.some((start) => href.startsWith(start))) {
|
||||
return [];
|
||||
}
|
||||
return 'noopener noreferrer nofollow';
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
output: 'hybrid',
|
||||
adapter: node({
|
||||
mode: 'standalone',
|
||||
}),
|
||||
trailingSlash: 'never',
|
||||
integrations: [
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false,
|
||||
},
|
||||
}),
|
||||
sitemap({
|
||||
filter: shouldIndexPage,
|
||||
serialize: serializeSitemap,
|
||||
}),
|
||||
react(),
|
||||
],
|
||||
});
|
||||
@@ -14,21 +14,21 @@ appearance, race, religion, or sexual identity and orientation.
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
60
components/content-page-header.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Box, Container, Flex, Heading, Image, Link, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
type ContentPageHeaderProps = {
|
||||
formattedDate: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
author?: {
|
||||
name: string;
|
||||
twitter: string;
|
||||
picture: string;
|
||||
},
|
||||
subLink?: {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
};
|
||||
|
||||
export function ContentPageHeader(props: ContentPageHeaderProps) {
|
||||
const { title, subtitle, author = null, formattedDate, subLink = null } = props;
|
||||
|
||||
return (
|
||||
<Box pt={['35px', '35px', '70px']} pb={['35px', '35px', '55px']} borderBottomWidth={1} mb='30px'>
|
||||
<Container maxW='container.md' position='relative' textAlign={['left', 'left', 'center']}>
|
||||
<Flex alignItems='center' justifyContent={['flex-start', 'flex-start', 'center']}
|
||||
fontSize={['12px', '12px', '14px']}>
|
||||
|
||||
{author?.name && (
|
||||
<>
|
||||
<Link
|
||||
d={['none', 'flex', 'flex']}
|
||||
target='_blank'
|
||||
href={`https://twitter.com/${author.twitter}`}
|
||||
alignItems='center'
|
||||
fontWeight={600}
|
||||
color='gray.500'
|
||||
>
|
||||
<Image alt={''} rounded={'full'} mr='7px' w='22px' src={author.picture} />
|
||||
{author.name}
|
||||
</Link>
|
||||
<Text d={['none', 'inline', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text color='gray.500' as='span'>{formattedDate}</Text>
|
||||
{subLink?.text && (
|
||||
<>
|
||||
<Text d={['none', 'none', 'inline']} mx='7px' color='gray.500' as='span'>·</Text>
|
||||
<Link d={['none', 'none', 'inline']} color='blue.500' fontWeight={500}
|
||||
href={subLink.url} target={'_blank'}>{subLink.text}</Link>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Heading as='h1' color='black' fontSize={['30px', '30px', '45px']} lineHeight={['40px', '40px', '53px']}
|
||||
fontWeight={700} my={['5px', '5px', '10px']}>{title}</Heading>
|
||||
<Text fontSize={['14px', '14px', '16px']} color='gray.700'>{subtitle}</Text>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
69
components/custom-ad.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
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://www.getambassador.io/edge-stack-guide-v4?utm_source=roadmap.sh&utm_medium=ebook&utm_campaign=edgestack-guide'
|
||||
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: `Ambassador EBook Redirect`,
|
||||
label: `Clicked Ambassador EBook Link`
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src='https://i.imgur.com/0bH1Vl6.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 eBook</Heading>
|
||||
<Box display='block' as='span' fontSize='13px' lineHeight={1.5} fontWeight={500} color='gray.500'>
|
||||
Learn about API Gateways, Microservices, Load Balancing, and more with this free eBook.
|
||||
</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>
|
||||
);
|
||||
};
|
||||
46
components/dimmed-more.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
type DimmedMoreProps = {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
export function DimmedMore(props: DimmedMoreProps) {
|
||||
const { text, href } = props;
|
||||
|
||||
return (
|
||||
<Box position='relative' textAlign='center' bottom='20px'>
|
||||
<Box
|
||||
opacity={1}
|
||||
pointerEvents='none'
|
||||
position='absolute'
|
||||
bottom={0}
|
||||
height='200px'
|
||||
width='100%'
|
||||
background='linear-gradient(180deg, rgb(255 255 255 / 40%), white)'
|
||||
/>
|
||||
|
||||
<Link
|
||||
rounded='20px'
|
||||
display='inline'
|
||||
bg='green.600'
|
||||
color='white'
|
||||
p='7px 20px'
|
||||
href={href}
|
||||
fontWeight={800}
|
||||
fontSize='11px'
|
||||
textTransform='uppercase'
|
||||
my='25px'
|
||||
position='relative'
|
||||
_hover={{
|
||||
textDecoration: 'none',
|
||||
'& .forward-arrow': {
|
||||
transform: 'translateX(3px)'
|
||||
}
|
||||
}}>
|
||||
{text}
|
||||
<Text d='inline-block' as='span' transition='200ms' ml='4px' className='forward-arrow'>→</Text>
|
||||
</Link>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
124
components/footer.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Box, Container, Flex, Image, Link, SimpleGrid, Stack, Text } from '@chakra-ui/react';
|
||||
import siteConfig from '../content/site.json';
|
||||
import { CustomAd } from './custom-ad';
|
||||
import React from 'react';
|
||||
import { event } from '../lib/gtag';
|
||||
|
||||
function NavigationLinks() {
|
||||
return (
|
||||
<>
|
||||
<Stack isInline display={['none', 'none', 'flex']} justifyContent='center' 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 display={['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 />
|
||||
|
||||
<SimpleGrid mt={['40px', '40px', '50px']} mb='40px' gap={['40px', '40px', '75px']} columns={[1, 1, 2, 2]}
|
||||
justifyContent='space-between'>
|
||||
<Box maxWidth={'550px'}>
|
||||
<Flex gap={0} alignItems='center' color='gray.400'>
|
||||
<Link d='flex' alignItems='center' fontWeight={600} _hover={{ textDecoration: 'none', color: 'white' }}
|
||||
href='/'>
|
||||
<Image alt='' h='25px' w='25px' src='/logo.svg' mr='6px' />
|
||||
roadmap.sh
|
||||
</Link>
|
||||
<Text as='span' mx='7px'>by</Text>
|
||||
<Link bg='blue.500' px='6px' py='2px' rounded='4px' color='white' fontWeight={600} fontSize='13px'
|
||||
_hover={{ textDecoration: 'none', bg: 'blue.600' }} href={siteConfig.url.twitter}
|
||||
target='_blank'>@kamranahmedse</Link>
|
||||
</Flex>
|
||||
|
||||
<Text my='15px' fontSize='14px' color='gray.500'>Community created roadmaps, articles, resources and
|
||||
journeys to help you choose your path and grow in your career.</Text>
|
||||
|
||||
<Text fontSize='14px' color='gray.500'>
|
||||
<Text as='span' mr='10px'>© roadmap.sh</Text>·
|
||||
<Link href='/about' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>FAQs</Link>·
|
||||
<Link href='/terms' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Terms</Link>·
|
||||
<Link href='/privacy' _hover={{ textDecoration: 'none', color: 'white' }} color='gray.400'
|
||||
mx='10px'>Privacy</Link>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box maxWidth={'550px'} textAlign={['left', 'left', 'right']}>
|
||||
<Link display='flex' justifyContent={['flex-start', 'flex-start', 'flex-end']} fontWeight={600}
|
||||
_hover={{ textDecoration: 'none', color: 'white' }}
|
||||
href='https://thenewstack.io?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Footer'
|
||||
target='_blank'>
|
||||
<Image alt='' w='195px' src='/tns.png' />
|
||||
</Link>
|
||||
|
||||
<Text my='15px' fontSize='14px' color='gray.500'>The leading DevOps resource for Kubernetes, cloud-native
|
||||
computing, and the latest in at-scale development, deployment, and management.</Text>
|
||||
|
||||
<Text fontSize='14px' color='gray.500'>
|
||||
<Link
|
||||
href='https://thenewstack.io/category/devops/?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Footer'
|
||||
target='_blank'
|
||||
_hover={{ textDecoration: 'none', color: 'white' }}
|
||||
onClick={() => {
|
||||
event({
|
||||
category: 'PartnerClick',
|
||||
action: `TNS Referral`,
|
||||
label: `TNS Referral - Footer`,
|
||||
});
|
||||
}}
|
||||
color='gray.400' mx='10px' ml={['0', '0', '10px']}>DevOps</Link>·
|
||||
<Link
|
||||
href='https://thenewstack.io/category/kubernetes/?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Footer'
|
||||
target='_blank' _hover={{ textDecoration: 'none', color: 'white' }}
|
||||
onClick={() => {
|
||||
event({
|
||||
category: 'PartnerClick',
|
||||
action: `TNS Referral`,
|
||||
label: `TNS Referral - Footer`,
|
||||
});
|
||||
}}
|
||||
color='gray.400' mx='10px'>Kubernetes</Link>·
|
||||
<Link
|
||||
href='https://thenewstack.io/category/cloud-native/?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Footer'
|
||||
target='_blank' _hover={{ textDecoration: 'none', color: 'white' }}
|
||||
onClick={() => {
|
||||
event({
|
||||
category: 'PartnerClick',
|
||||
action: `TNS Referral`,
|
||||
label: `TNS Referral - Footer`,
|
||||
});
|
||||
}}
|
||||
color='gray.400' mx='10px'>Cloud-Native</Link>
|
||||
</Text>
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Container>
|
||||
|
||||
<CustomAd />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
134
components/global-header.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useState } from 'react';
|
||||
import { HamburgerIcon } from '@chakra-ui/icons';
|
||||
import { Box, CloseButton, Container, Flex, IconButton, Link, Stack, Text } from '@chakra-ui/react';
|
||||
import RoadmapLogo from '../components/icons/roadmap.svg';
|
||||
|
||||
type MenuLinkProps = {
|
||||
text: string;
|
||||
link: string;
|
||||
target?: '_blank' | '_self' | '_parent' | '_top';
|
||||
isFancy?: boolean;
|
||||
};
|
||||
|
||||
function MenuLink(props: MenuLinkProps) {
|
||||
const { text, link, target = '_self', isFancy = false } = props;
|
||||
|
||||
const gradientProp = isFancy ? {
|
||||
bgGradient: 'linear(to-r, yellow.100, teal.100)',
|
||||
bgClip: 'text',
|
||||
_hover: {
|
||||
color: 'yellow.100'
|
||||
}
|
||||
} : {};
|
||||
|
||||
return <Link
|
||||
borderBottomWidth={0}
|
||||
borderBottomColor='gray.500'
|
||||
_hover={{ textDecoration: 'none', borderBottomColor: 'white' }}
|
||||
fontWeight={500}
|
||||
href={link}
|
||||
target={target}
|
||||
{...gradientProp}
|
||||
>
|
||||
{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
|
||||
target={'_blank'}
|
||||
text={'Hiring a DevRel'}
|
||||
isFancy
|
||||
link={'https://docs.google.com/forms/d/e/1FAIpQLSesFpPxgKx_8-L5hm7fw6NQpgGixrMGC4Cg3M8NHPQhFfSajQ/viewform'}
|
||||
/>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
33
components/guide/guide-grid-item.tsx
Normal 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
@@ -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;
|
||||
73
components/home/featured-roadmaps-list.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
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: 'Role Based',
|
||||
title: 'Cyber Security',
|
||||
description: 'Step by step guide to become a Cyber Security Expert',
|
||||
id: 'cyber-security'
|
||||
},
|
||||
// {
|
||||
// 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
components/icons/facebook-square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h137.25V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.27c-30.81 0-40.42 19.12-40.42 38.73V256h68.78l-11 71.69h-57.78V480H400a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48z"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
3
components/icons/facebook.svg
Normal 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 |
3
components/icons/github.svg
Normal 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 |
1
components/icons/hackernews-square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM21.2 229.2H21c.1-.1.2-.3.3-.4 0 .1 0 .3-.1.4zm218 53.9V384h-31.4V281.3L128 128h37.3c52.5 98.3 49.2 101.2 59.3 125.6 12.3-27 5.8-24.4 60.6-125.6H320l-80.8 155.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 515 B |
4
components/icons/link.svg
Normal 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 |
1
components/icons/reddit-square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M283.2 345.5c2.7 2.7 2.7 6.8 0 9.2-24.5 24.5-93.8 24.6-118.4 0-2.7-2.4-2.7-6.5 0-9.2 2.4-2.4 6.5-2.4 8.9 0 18.7 19.2 81 19.6 100.5 0 2.4-2.3 6.6-2.3 9 0zm-91.3-53.8c0-14.9-11.9-26.8-26.5-26.8-14.9 0-26.8 11.9-26.8 26.8 0 14.6 11.9 26.5 26.8 26.5 14.6 0 26.5-11.9 26.5-26.5zm90.7-26.8c-14.6 0-26.5 11.9-26.5 26.8 0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-11.9 26.8-26.5 0-14.9-11.9-26.8-26.8-26.8zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-99.7 140.6c-10.1 0-19 4.2-25.6 10.7-24.1-16.7-56.5-27.4-92.5-28.6l18.7-84.2 59.5 13.4c0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-12.2 26.8-26.8 0-14.6-11.9-26.8-26.8-26.8-10.4 0-19.3 6.2-23.8 14.9l-65.7-14.6c-3.3-.9-6.5 1.5-7.4 4.8l-20.5 92.8c-35.7 1.5-67.8 12.2-91.9 28.9-6.5-6.8-15.8-11-25.9-11-37.5 0-49.8 50.4-15.5 67.5-1.2 5.4-1.8 11-1.8 16.7 0 56.5 63.7 102.3 141.9 102.3 78.5 0 142.2-45.8 142.2-102.3 0-5.7-.6-11.6-2.1-17 33.6-17.2 21.2-67.2-16.1-67.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
components/icons/roadmap.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 283 283" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 39C0 17.4609 17.4609 0 39 0H244C265.539 0 283 17.4609 283 39V244C283 265.539 265.539 283 244 283H39C17.4609 283 0 265.539 0 244V39Z" fill="black"></path>
|
||||
<path d="M121.215 210.72C119.348 211.28 116.361 211.84 112.255 212.4C108.335 212.96 104.228 213.24 99.9347 213.24C95.828 213.24 92.0947 212.96 88.7347 212.4C85.5614 211.84 82.8547 210.72 80.6147 209.04C78.3747 207.36 76.6014 205.12 75.2947 202.32C74.1747 199.333 73.6147 195.507 73.6147 190.84V106.84C73.6147 102.547 74.3614 98.9067 75.8547 95.92C77.5347 92.7467 79.868 89.9467 82.8547 87.52C85.8414 85.0933 89.4814 82.9467 93.7747 81.08C98.2547 79.0267 103.015 77.2533 108.055 75.76C113.095 74.2667 118.321 73.1467 123.735 72.4C129.148 71.4667 134.561 71 139.975 71C148.935 71 156.028 72.7733 161.255 76.32C166.481 79.68 169.095 85.28 169.095 93.12C169.095 95.7333 168.721 98.3467 167.975 100.96C167.228 103.387 166.295 105.627 165.175 107.68C161.255 107.68 157.241 107.867 153.135 108.24C149.028 108.613 145.015 109.173 141.095 109.92C137.175 110.667 133.441 111.507 129.895 112.44C126.535 113.187 123.641 114.12 121.215 115.24V210.72ZM166.387 188.32C166.387 180.48 168.813 173.947 173.667 168.72C178.52 163.493 185.147 160.88 193.547 160.88C201.947 160.88 208.573 163.493 213.427 168.72C218.28 173.947 220.707 180.48 220.707 188.32C220.707 196.16 218.28 202.693 213.427 207.92C208.573 213.147 201.947 215.76 193.547 215.76C185.147 215.76 178.52 213.147 173.667 207.92C168.813 202.693 166.387 196.16 166.387 188.32Z" fill="white"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
components/icons/tree.svg
Normal 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 |
1
components/icons/twitter-square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-48.9 158.8c.2 2.8.2 5.7.2 8.5 0 86.7-66 186.6-186.6 186.6-37.2 0-71.7-10.8-100.7-29.4 5.3.6 10.4.8 15.8.8 30.7 0 58.9-10.4 81.4-28-28.8-.6-53-19.5-61.3-45.5 10.1 1.5 19.2 1.5 29.6-1.2-30-6.1-52.5-32.5-52.5-64.4v-.8c8.7 4.9 18.9 7.9 29.6 8.3a65.447 65.447 0 0 1-29.2-54.6c0-12.2 3.2-23.4 8.9-33.1 32.3 39.8 80.8 65.8 135.2 68.6-9.3-44.5 24-80.6 64-80.6 18.9 0 35.9 7.9 47.9 20.7 14.8-2.8 29-8.3 41.6-15.8-4.9 15.2-15.2 28-28.8 36.1 13.2-1.4 26-5.1 37.8-10.2-8.9 13.1-20.1 24.7-32.9 34z"/></svg>
|
||||
|
After Width: | Height: | Size: 840 B |
3
components/icons/twitter.svg
Normal 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 |
21
components/icons/video-icon.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
export function VideoIcon(props: any) {
|
||||
return (
|
||||
<svg
|
||||
stroke='currentColor'
|
||||
fill='currentColor'
|
||||
strokeWidth='0'
|
||||
viewBox='0 0 24 24'
|
||||
height='1em'
|
||||
width='1em'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<path fill='none' d='M0 0h24v24H0z' />
|
||||
<path
|
||||
d='M3 3.993C3 3.445 3.445 3 3.993 3h16.014c.548 0 .993.445.993.993v16.014a.994.994 0 0 1-.993.993H3.993A.994.994 0 0 1 3 20.007V3.993zm7.622 4.422a.4.4 0 0 0-.622.332v6.506a.4.4 0 0 0 .622.332l4.879-3.252a.4.4 0 0 0 0-.666l-4.88-3.252z'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
3
components/icons/youtube.svg
Normal file
@@ -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 |
58
components/links-list-item.tsx
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
||||
20
components/md-renderer/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
// @ts-ignore
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import MdxComponents from './mdx-components';
|
||||
import { roadmapTheme } from '../../styles/theme';
|
||||
|
||||
type MdRendererType = {
|
||||
children: React.ReactNode
|
||||
};
|
||||
|
||||
export default function MdRenderer(props: MdRendererType) {
|
||||
return (
|
||||
<ChakraProvider theme={roadmapTheme} resetCSS>
|
||||
<MDXProvider components={MdxComponents}>
|
||||
{props.children}
|
||||
</MDXProvider>
|
||||
</ChakraProvider>
|
||||
);
|
||||
};
|
||||
37
components/md-renderer/mdx-components/a.tsx
Normal 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;
|
||||
53
components/md-renderer/mdx-components/badge-link.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
27
components/md-renderer/mdx-components/blockquote.tsx
Normal 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;
|
||||
10
components/md-renderer/mdx-components/code.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Code as ChakraCode } from '@chakra-ui/react';
|
||||
|
||||
type CodeType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Code(props: CodeType) {
|
||||
return <ChakraCode bg='blue.500'>{props.children}</ChakraCode>;
|
||||
}
|
||||
22
components/md-renderer/mdx-components/dedicated-roadmap.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
81
components/md-renderer/mdx-components/heading.tsx
Normal 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;
|
||||
47
components/md-renderer/mdx-components/iframe.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
type IFrameProps = {
|
||||
title: string;
|
||||
src: string;
|
||||
};
|
||||
|
||||
const AspectRatioBox = styled.div`
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:before {
|
||||
height: 0;
|
||||
content: "";
|
||||
display: block;
|
||||
padding-bottom: 50%;
|
||||
}
|
||||
|
||||
& > iframe {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function IFrame(props: IFrameProps) {
|
||||
return (
|
||||
<AspectRatioBox>
|
||||
<iframe
|
||||
frameBorder={0}
|
||||
title={props.title}
|
||||
src={props.src}
|
||||
allow={'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'}
|
||||
allowFullScreen
|
||||
/>
|
||||
</AspectRatioBox>
|
||||
);
|
||||
}
|
||||
7
components/md-renderer/mdx-components/img.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Img = styled.img`
|
||||
max-width: 100%;
|
||||
margin: 25px auto;
|
||||
display: block;
|
||||
`;
|
||||
34
components/md-renderer/mdx-components/index.tsx
Normal 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;
|
||||
14
components/md-renderer/mdx-components/p.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type EnrichedTextType = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const P = styled.p`
|
||||
line-height: 27px;
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
margin-bottom: 18px;
|
||||
`;
|
||||
12
components/md-renderer/mdx-components/pre.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
19
components/md-renderer/mdx-components/premium-block.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Heading, Text } from '@chakra-ui/react';
|
||||
import { LockIcon } from '@chakra-ui/icons';
|
||||
|
||||
type PremiumBlockProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export default function PremiumBlock(props: PremiumBlockProps) {
|
||||
return (
|
||||
<Box p='40px' textAlign='center' rounded='5px' mb='18px' bg='gray.50' borderWidth={1}>
|
||||
<LockIcon color='gray.300' height='45px' w='45px' mb='18px' />
|
||||
<Heading as='h3' fontSize='30px' mb='10px'>{props.title}</Heading>
|
||||
<Text mb='18px'>{props.description}</Text>
|
||||
<Button colorScheme='green'>Become a Member</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
25
components/md-renderer/mdx-components/table.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
16
components/md-renderer/mdx-components/ul.tsx
Normal 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;
|
||||
`;
|
||||
29
components/opensource-banner.tsx
Normal 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,
|
||||
<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>
|
||||
);
|
||||
}
|
||||
37
components/page-header.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
16
components/page-wrapper.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
80
components/related-roadmaps.tsx
Normal 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 →</Text>
|
||||
<Text as='span' display={['none', 'inline', 'inline']}>All Roadmaps →</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>
|
||||
);
|
||||
}
|
||||
131
components/roadmap/content-drawer.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
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 markTopicDone(groupId: string) {
|
||||
localStorage.setItem(groupId, 'done');
|
||||
|
||||
queryGroupElementsById(groupId).forEach((item) =>
|
||||
item?.classList?.add('done')
|
||||
);
|
||||
}
|
||||
|
||||
export function markTopicPending(groupId: string) {
|
||||
localStorage.removeItem(groupId);
|
||||
|
||||
queryGroupElementsById(groupId).forEach((item) =>
|
||||
item?.classList?.remove('done')
|
||||
);
|
||||
}
|
||||
|
||||
export function isTopicDone(groupId: string) {
|
||||
return localStorage.getItem(groupId) === 'done';
|
||||
}
|
||||
|
||||
export function ContentDrawer(props: ContentDrawerProps) {
|
||||
const { roadmap, groupId, onClose = () => null } = props;
|
||||
if (!groupId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isDone = isTopicDone(groupId);
|
||||
|
||||
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={() => {
|
||||
markTopicDone(groupId);
|
||||
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={() => {
|
||||
markTopicPending(groupId);
|
||||
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>
|
||||
);
|
||||
}
|
||||
39
components/roadmap/edit-content-page-link.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
121
components/roadmap/home-roadmap-item.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
50
components/roadmap/new-alert-banner.tsx
Normal 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">
|
||||
»
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
26
components/roadmap/roadmap-error.tsx
Normal 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'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>
|
||||
);
|
||||
}
|
||||
89
components/roadmap/roadmap-grid-item.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Badge, Box, Flex, Heading, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { InfoIcon } from '@chakra-ui/icons';
|
||||
|
||||
type RoadmapGridItemProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
href: string;
|
||||
isCommunity?: boolean;
|
||||
isUpcoming?: boolean;
|
||||
colorIndex?: number;
|
||||
};
|
||||
|
||||
const bgColorList = [
|
||||
'gray.900',
|
||||
'purple.900',
|
||||
'blue.900',
|
||||
'red.900',
|
||||
'green.900',
|
||||
'teal.900',
|
||||
'yellow.900',
|
||||
'cyan.900',
|
||||
'pink.900',
|
||||
|
||||
'gray.800',
|
||||
'purple.800',
|
||||
'blue.800',
|
||||
'red.800',
|
||||
'green.800',
|
||||
'teal.800',
|
||||
'yellow.800',
|
||||
'cyan.800',
|
||||
'pink.800',
|
||||
|
||||
'gray.700',
|
||||
'purple.700',
|
||||
'blue.700',
|
||||
'red.700',
|
||||
'green.700',
|
||||
'teal.700',
|
||||
'yellow.700',
|
||||
'cyan.700',
|
||||
'pink.700',
|
||||
|
||||
'gray.600',
|
||||
'purple.600',
|
||||
'blue.600',
|
||||
'red.600',
|
||||
'green.600',
|
||||
'teal.600',
|
||||
'yellow.600',
|
||||
'cyan.600',
|
||||
'pink.600'
|
||||
];
|
||||
|
||||
export function RoadmapGridItem(props: RoadmapGridItemProps) {
|
||||
const { title, subtitle, isCommunity = false, isUpcoming = false, colorIndex = 0, href = '/' } = props;
|
||||
|
||||
return (
|
||||
<Box _hover={{ textDecoration: 'none', transform: 'scale(1.02)' }} as={Link} href={href} shadow='xl' p='20px'
|
||||
rounded='10px' bg={bgColorList[colorIndex] ?? bgColorList[0]} flex={1} pos='relative'>
|
||||
|
||||
{isCommunity && (
|
||||
<Tooltip label={'Community contribution'} hasArrow placement='top'>
|
||||
<InfoIcon opacity={0.5} color='gray.100' position='absolute' top='10px' right='10px' />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Heading color='white' mb={'6px'} fontSize='20px'>{title}</Heading>
|
||||
<Text color='gray.300' fontSize='14px'>{subtitle}</Text>
|
||||
|
||||
{isUpcoming && (
|
||||
<Flex
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
pos='absolute'
|
||||
left={0}
|
||||
right={0}
|
||||
top={0}
|
||||
bottom={0}
|
||||
rounded='10px'
|
||||
>
|
||||
<Text color='white' bg='yellow.900' zIndex={1} fontWeight={600} p={'5px 10px'}
|
||||
rounded='10px'>Upcoming</Text>
|
||||
<Box bg={'black'} pos='absolute' top={0} left={0} right={0} bottom={0} rounded={'10px'} opacity={0.5} />
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
20
components/roadmap/roadmap-loader.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
223
components/roadmap/roadmap-page-header.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
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';
|
||||
import { TNSAlert } from './tns-alert';
|
||||
|
||||
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 email 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;
|
||||
|
||||
const hasTNSAlert = ['frontend', 'backend', 'devops'].includes(roadmap.id);
|
||||
|
||||
return (
|
||||
<Box
|
||||
pt={['25px', '20px', '45px']}
|
||||
pb={['20px', '15px', '30px']}
|
||||
borderBottomWidth={1}
|
||||
mb='50px'
|
||||
>
|
||||
<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
|
||||
display={['flex', 'flex']}
|
||||
as={Link}
|
||||
href={'/roadmaps'}
|
||||
size='xs'
|
||||
py='14px'
|
||||
px='10px'
|
||||
colorScheme='teal'
|
||||
variant='solid'
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
>
|
||||
←
|
||||
<Text as='span' display={['none', 'inline']} ml='5px'>
|
||||
All Roadmaps
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<RoadmapDownloader roadmapTitle={roadmap.featuredTitle} />
|
||||
<RoadmapSubscriber roadmapTitle={roadmap.featuredTitle} />
|
||||
|
||||
<Box flex={1} justifyContent='flex-end' display='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) && (
|
||||
<Box mt='30px' mb={hasTNSAlert ? ['-53px', '-48px', '-63px'] : ['-37px', '-32px', '-47px']} borderWidth={1} rounded='3px'>
|
||||
{ hasTNSAlert && <TNSAlert roadmapName={roadmap.featuredTitle} />}
|
||||
<Text
|
||||
fontWeight={500}
|
||||
fontSize='14px'
|
||||
bg='white'
|
||||
p='5px 7px'
|
||||
rounded='3px'
|
||||
>
|
||||
<Badge pos='relative' top={'-1px'} mr='6px' colorScheme='yellow'>
|
||||
New
|
||||
</Badge>
|
||||
Resources are here, try clicking any nodes.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
52
components/roadmap/tns-alert.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Box, Link, Text } from '@chakra-ui/react';
|
||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import React from 'react';
|
||||
import { event } from '../../lib/gtag';
|
||||
|
||||
type TNSAlertProps = {
|
||||
roadmapName: string;
|
||||
};
|
||||
|
||||
export function TNSAlert(props: TNSAlertProps) {
|
||||
const { roadmapName } = props;
|
||||
|
||||
return (
|
||||
<Text
|
||||
fontWeight={500}
|
||||
fontSize='14px'
|
||||
bg='gray.100'
|
||||
p='5px 7px'
|
||||
rounded='2px 2px 0 0'
|
||||
borderBottomWidth={1}
|
||||
>
|
||||
<Box as='span' display={['none', 'none', 'inline']}>Get the latest {roadmapName} news from our sister site
|
||||
<Link
|
||||
href={'https://thenewstack.io?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Banner'}
|
||||
target='_blank' textDecoration='underline'
|
||||
onClick={() => {
|
||||
event({
|
||||
category: 'PartnerClick',
|
||||
action: `TNS Referral`,
|
||||
label: `TNS Referral - ${roadmapName}`,
|
||||
});
|
||||
}}
|
||||
fontWeight={600}>TheNewStack.io <ExternalLinkIcon />
|
||||
</Link>
|
||||
</Box>
|
||||
<Box as='span' display={['inline', 'inline', 'none']}>Get latest {roadmapName} news on
|
||||
<Link
|
||||
href={'https://thenewstack.io?utm_source=roadmap-sh&utm_medium=Referral&utm_campaign=Banner'}
|
||||
target='_blank' textDecoration='underline'
|
||||
onClick={() => {
|
||||
event({
|
||||
category: 'PartnerClick',
|
||||
action: `TNS Referral`,
|
||||
label: `TNS Referral - ${roadmapName}`,
|
||||
});
|
||||
}}
|
||||
fontWeight={600}>TheNewStack.io <ExternalLinkIcon />
|
||||
</Link>
|
||||
</Box>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
43
components/share-icons.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Box, Flex, Link } from '@chakra-ui/react';
|
||||
import HackerNewsIcon from 'components/icons/hackernews-square.svg';
|
||||
import FacebookIcon from 'components/icons/facebook-square.svg';
|
||||
import TwitterIcon from 'components/icons/twitter-square.svg';
|
||||
import RedditIcon from 'components/icons/reddit-square.svg';
|
||||
import { Icon } from '@chakra-ui/icons';
|
||||
import { getFacebookShareUrl, getHnShareUrl, getRedditShareUrl, getTwitterShareUrl } from '../lib/url';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type ShareIconProps = {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function ShareIcons(props: ShareIconProps) {
|
||||
const { text, url } = props;
|
||||
|
||||
const [offset, setOffset] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setOffset(window.scrollY);
|
||||
|
||||
window.removeEventListener('scroll', onScroll);
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
if (offset <= 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box pos='absolute' left={'-15px'} top={'190px'} height='100%' display={['none', 'none', 'none', 'block']}>
|
||||
<Flex pos='sticky' top='100px' flexDir='column'>
|
||||
<Link target='_blank' color='gray.500' href={getTwitterShareUrl({ url, text })} _hover={{ color: "gray.700" }}><Icon fill='currentColor' height='24px' width='24px' as={TwitterIcon} /></Link>
|
||||
<Link target='_blank' color='gray.500' href={getFacebookShareUrl({ url, text })} _hover={{ color: "gray.700" }}><Icon fill='currentColor' height='24px' width='24px' as={FacebookIcon} /></Link>
|
||||
<Link target='_blank' color='gray.500' href={getHnShareUrl({ url, text })} _hover={{ color: "gray.700" }}><Icon fill='currentColor' height='24px' width='24px' as={HackerNewsIcon} /></Link>
|
||||
<Link target='_blank' color='gray.500' href={getRedditShareUrl({ text, url })} _hover={{ color: "gray.700" }}><Icon fill='currentColor' height='24px' width='24px' as={RedditIcon} /></Link>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
33
components/sticky-banner.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
22
components/teams-banner.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Box, Button, Container, Heading, Link, Text } from '@chakra-ui/react';
|
||||
import { event } from '../lib/gtag';
|
||||
|
||||
export function TeamsBanner() {
|
||||
return null;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
69
components/watch/video-grid-item.tsx
Normal 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
@@ -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"
|
||||
}
|
||||
]
|
||||
314
content/guides.json
Normal file
@@ -0,0 +1,314 @@
|
||||
[
|
||||
{
|
||||
"id": "session-based-authentication",
|
||||
"title": "Session Based Authentication",
|
||||
"description": "Learn what is Session Based Authentication and how to implement it in Node.js",
|
||||
"isNew": true,
|
||||
"type": "textual",
|
||||
"authorUsername": "kamranahmedse",
|
||||
"updatedAt": "2022-11-01T19:59:14.191Z",
|
||||
"createdAt": "2022-11-01T19:59:14.191Z"
|
||||
},
|
||||
{
|
||||
"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": false,
|
||||
"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 won’t 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"
|
||||
}
|
||||
]
|
||||
4
content/guides/asymptotic-notation.md
Normal file
@@ -0,0 +1,4 @@
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[](/guides/avoid-render-blocking-javascript-with-async-defer.png)
|
||||
|
||||
2
content/guides/basic-authentication.md
Normal file
@@ -0,0 +1,2 @@
|
||||
[](/guides/basic-authentication.png)
|
||||
|
||||
83
content/guides/basics-of-authentication.md
Normal 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.
|
||||
4
content/guides/big-o-notation.md
Normal 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)
|
||||
|
||||
2
content/guides/character-encodings.md
Normal file
@@ -0,0 +1,2 @@
|
||||
[](/guides/character-encodings.png)
|
||||
|
||||
4
content/guides/ci-cd.md
Normal 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)
|
||||
|
||||
@@ -1,22 +1,3 @@
|
||||
---
|
||||
title: 'Design Patterns for Humans'
|
||||
description: 'A language agnostic, ultra-simplified explanation to design patterns'
|
||||
authorId: 'kamran'
|
||||
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
|
||||
@@ -30,7 +11,7 @@ Wikipedia describes them as
|
||||
Developers, mostly beginners, make the mistake of over-thinking and forcing the design patterns which results in a horrible un-maintainable mess. The rule of thumb is to keep the codebase as simple as possible, once you start developing, you will start to see the patterns repeating in the codebase in which case you can go ahead and implement the relevant design patterns.
|
||||
|
||||
- Design patterns are not a silver bullet to all your problems.
|
||||
- Do not try to force them; bad things are supposed to happen, if done so.
|
||||
- Do not try to force them; bad things are supposed to happen, if done so.
|
||||
- Keep in mind that design patterns are solutions **to** problems, not solutions **finding** problems; so don't overthink.
|
||||
- If used in a correct place in a correct manner, they can prove to be a savior; or else they can result in a horrible mess of a code.
|
||||
|
||||
@@ -40,47 +21,41 @@ Developers, mostly beginners, make the mistake of over-thinking and forcing the
|
||||
|
||||
This guide is about Gang of Four (GoF) design patterns, which refers to the four authors of [the book which introduced these design patterns](https://en.wikipedia.org/wiki/Design_Patterns). There are three types of design patterns:
|
||||
|
||||
- [Creational](#creational-design-patterns)
|
||||
- [Structural](#structural-design-patterns)
|
||||
- [Behavioral](#behavioral-design-patterns)
|
||||
* [Creational](#creational-design-patterns)
|
||||
* [Structural](#structural-design-patterns)
|
||||
* [Behavioral](#behavioral-design-patterns)
|
||||
|
||||
## Creational Design Patterns
|
||||
|
||||
In plain words
|
||||
|
||||
> Creational patterns are focused towards how to instantiate an object or group of related objects.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, creational design patterns are design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation.
|
||||
|
||||
There are 6 types of Creational patterns
|
||||
|
||||
- [Simple Factory](#-simple-factory)
|
||||
- [Factory Method](#-factory-method)
|
||||
- [Abstract Factory](#-abstract-factory)
|
||||
- [Builder](#-builder)
|
||||
- [Prototype](#-prototype)
|
||||
- [Singleton](#-singleton)
|
||||
|
||||
## 🏠 Simple Factory
|
||||
* [Simple Factory](#-simple-factory)
|
||||
* [Factory Method](#-factory-method)
|
||||
* [Abstract Factory](#-abstract-factory)
|
||||
* [Builder](#-builder)
|
||||
* [Prototype](#-prototype)
|
||||
* [Singleton](#-singleton)
|
||||
|
||||
🏠 Simple Factory
|
||||
--------------
|
||||
Real world example
|
||||
|
||||
> Consider, you are building a house and you need doors. You can either put on your carpenter clothes, bring some wood, glue, nails and all the tools required to build the door and start building it in your house or you can simply call the factory and get the built door delivered to you so that you don't need to learn anything about the door making or to deal with the mess that comes with making it.
|
||||
|
||||
In plain words
|
||||
|
||||
> Simple factory simply generates an instance for client without exposing any instantiation logic to the client
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new".
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
First of all we have a door interface and the implementation
|
||||
|
||||
```php
|
||||
interface Door
|
||||
{
|
||||
@@ -110,9 +85,7 @@ class WoodenDoor implements Door
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have our door factory that makes the door and returns it
|
||||
|
||||
```php
|
||||
class DoorFactory
|
||||
{
|
||||
@@ -122,9 +95,7 @@ class DoorFactory
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
// Make me a door of 100x200
|
||||
$door = DoorFactory::makeDoor(100, 200);
|
||||
@@ -140,21 +111,19 @@ $door2 = DoorFactory::makeDoor(50, 100);
|
||||
|
||||
When creating an object is not just a few assignments and involves some logic, it makes sense to put it in a dedicated factory instead of repeating the same code everywhere.
|
||||
|
||||
## 🏭 Factory Method
|
||||
🏭 Factory Method
|
||||
--------------
|
||||
|
||||
Real world example
|
||||
|
||||
> Consider the case of a hiring manager. It is impossible for one person to interview for each of the positions. Based on the job opening, she has to decide and delegate the interview steps to different people.
|
||||
|
||||
In plain words
|
||||
|
||||
> It provides a way to delegate the instantiation logic to child classes.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.
|
||||
|
||||
**Programmatic Example**
|
||||
**Programmatic Example**
|
||||
|
||||
Taking our hiring manager example above. First of all we have an interviewer interface and some implementations for it
|
||||
|
||||
@@ -198,9 +167,7 @@ abstract class HiringManager
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Now any child can extend it and provide the required interviewer
|
||||
|
||||
```php
|
||||
class DevelopmentManager extends HiringManager
|
||||
{
|
||||
@@ -218,7 +185,6 @@ class MarketingManager extends HiringManager
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and then it can be used as
|
||||
|
||||
```php
|
||||
@@ -233,18 +199,16 @@ $marketingManager->takeInterview(); // Output: Asking about community building.
|
||||
|
||||
Useful when there is some generic processing in a class but the required sub-class is dynamically decided at runtime. Or putting it in other words, when the client doesn't know what exact sub-class it might need.
|
||||
|
||||
## 🔨 Abstract Factory
|
||||
🔨 Abstract Factory
|
||||
----------------
|
||||
|
||||
Real world example
|
||||
|
||||
> Extending our door example from Simple Factory. Based on your needs you might get a wooden door from a wooden door shop, iron door from an iron shop or a PVC door from the relevant shop. Plus you might need a guy with different kind of specialities to fit the door, for example a carpenter for wooden door, welder for iron door etc. As you can see there is a dependency between the doors now, wooden door needs carpenter, iron door needs a welder etc.
|
||||
|
||||
In plain words
|
||||
|
||||
> A factory of factories; a factory that groups the individual but related/dependent factories together without specifying their concrete classes.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -273,7 +237,6 @@ class IronDoor implements Door
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have some fitting experts for each door type
|
||||
|
||||
```php
|
||||
@@ -300,7 +263,6 @@ class Carpenter implements DoorFittingExpert
|
||||
```
|
||||
|
||||
Now we have our abstract factory that would let us make family of related objects i.e. wooden door factory would create a wooden door and wooden door fitting expert and iron door factory would create an iron door and iron door fitting expert
|
||||
|
||||
```php
|
||||
interface DoorFactory
|
||||
{
|
||||
@@ -336,9 +298,7 @@ class IronDoorFactory implements DoorFactory
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
$woodenFactory = new WoodenDoorFactory();
|
||||
|
||||
@@ -358,24 +318,20 @@ $door->getDescription(); // Output: I am an iron door
|
||||
$expert->getDescription(); // Output: I can only fit iron doors
|
||||
```
|
||||
|
||||
As you can see the wooden door factory has encapsulated the `carpenter` and the `wooden door` also iron door factory has encapsulated the `iron door` and `welder`. And thus it had helped us make sure that for each of the created door, we do not get a wrong fitting expert.
|
||||
As you can see the wooden door factory has encapsulated the `carpenter` and the `wooden door` also iron door factory has encapsulated the `iron door` and `welder`. And thus it had helped us make sure that for each of the created door, we do not get a wrong fitting expert.
|
||||
|
||||
**When to use?**
|
||||
|
||||
When there are interrelated dependencies with not-that-simple creation logic involved
|
||||
|
||||
## 👷 Builder
|
||||
|
||||
Real world example
|
||||
|
||||
> Imagine you are at Hardee's and you order a specific deal, lets say, "Big Hardee" and they hand it over to you without _any questions_; this is the example of simple factory. But there are cases when the creation logic might involve more steps. For example you want a customized Subway deal, you have several options in how your burger is made e.g what bread do you want? what types of sauces would you like? What cheese would you want? etc. In such cases builder pattern comes to the rescue.
|
||||
> Imagine you are at Hardee's and you order a specific deal, lets say, "Big Hardee" and they hand it over to you without *any questions*; this is the example of simple factory. But there are cases when the creation logic might involve more steps. For example you want a customized Subway deal, you have several options in how your burger is made e.g what bread do you want? what types of sauces would you like? What cheese would you want? etc. In such cases builder pattern comes to the rescue.
|
||||
|
||||
In plain words
|
||||
|
||||
> Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.
|
||||
|
||||
Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point or the other we have all seen a constructor like below:
|
||||
@@ -460,7 +416,6 @@ class BurgerBuilder
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as:
|
||||
|
||||
```php
|
||||
@@ -475,18 +430,15 @@ $burger = (new BurgerBuilder(14))
|
||||
|
||||
When there could be several flavors of an object and to avoid the constructor telescoping. The key difference from the factory pattern is that; factory pattern is to be used when the creation is a one step process while builder pattern is to be used when the creation is a multi step process.
|
||||
|
||||
## 🐑 Prototype
|
||||
|
||||
🐑 Prototype
|
||||
------------
|
||||
Real world example
|
||||
|
||||
> Remember dolly? The sheep that was cloned! Lets not get into the details but the key point here is that it is all about cloning
|
||||
|
||||
In plain words
|
||||
|
||||
> Create object based on an existing object through cloning.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.
|
||||
|
||||
In short, it allows you to create a copy of an existing object and modify it to your needs, instead of going through the trouble of creating an object from scratch and setting it up.
|
||||
@@ -528,9 +480,7 @@ class Sheep
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then it can be cloned like below
|
||||
|
||||
```php
|
||||
$original = new Sheep('Jolly');
|
||||
echo $original->getName(); // Jolly
|
||||
@@ -549,18 +499,15 @@ Also you could use the magic method `__clone` to modify the cloning behavior.
|
||||
|
||||
When an object is required that is similar to existing object or when the creation would be expensive as compared to cloning.
|
||||
|
||||
## 💍 Singleton
|
||||
|
||||
💍 Singleton
|
||||
------------
|
||||
Real world example
|
||||
|
||||
> There can only be one president of a country at a time. The same president has to be brought to action, whenever duty calls. President here is singleton.
|
||||
|
||||
In plain words
|
||||
|
||||
> Ensures that only one object of a particular class is ever created.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
|
||||
|
||||
Singleton pattern is actually considered an anti-pattern and overuse of it should be avoided. It is not necessarily bad and could have some valid use-cases but should be used with caution because it introduces a global state in your application and change to it in one place could affect in the other areas and it could become pretty difficult to debug. The other bad thing about them is it makes your code tightly coupled plus mocking the singleton could be difficult.
|
||||
@@ -568,7 +515,6 @@ Singleton pattern is actually considered an anti-pattern and overuse of it shoul
|
||||
**Programmatic Example**
|
||||
|
||||
To create a singleton, make the constructor private, disable cloning, disable extension and create a static variable to house the instance
|
||||
|
||||
```php
|
||||
final class President
|
||||
{
|
||||
@@ -599,9 +545,7 @@ final class President
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then in order to use
|
||||
|
||||
```php
|
||||
$president1 = President::getInstance();
|
||||
$president2 = President::getInstance();
|
||||
@@ -610,39 +554,33 @@ var_dump($president1 === $president2); // true
|
||||
```
|
||||
|
||||
## Structural Design Patterns
|
||||
|
||||
In plain words
|
||||
|
||||
> Structural patterns are mostly concerned with object composition or in other words how the entities can use each other. Or yet another explanation would be, they help in answering "How to build a software component?"
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.
|
||||
|
||||
There are 7 types of structural patterns
|
||||
|
||||
- [Adapter](#-adapter)
|
||||
- [Bridge](#-bridge)
|
||||
- [Composite](#-composite)
|
||||
- [Decorator](#-decorator)
|
||||
- [Facade](#-facade)
|
||||
- [Flyweight](#-flyweight)
|
||||
- [Proxy](#-proxy)
|
||||
|
||||
## 🔌 Adapter
|
||||
* [Adapter](#-adapter)
|
||||
* [Bridge](#-bridge)
|
||||
* [Composite](#-composite)
|
||||
* [Decorator](#-decorator)
|
||||
* [Facade](#-facade)
|
||||
* [Flyweight](#-flyweight)
|
||||
* [Proxy](#-proxy)
|
||||
|
||||
🔌 Adapter
|
||||
-------
|
||||
Real world example
|
||||
|
||||
> Consider that you have some pictures in your memory card and you need to transfer them to your computer. In order to transfer them you need some kind of adapter that is compatible with your computer ports so that you can attach memory card to your computer. In this case card reader is an adapter.
|
||||
> Another example would be the famous power adapter; a three legged plug can't be connected to a two pronged outlet, it needs to use a power adapter that makes it compatible with the two pronged outlet.
|
||||
> Yet another example would be a translator translating words spoken by one person to another
|
||||
|
||||
In plain words
|
||||
|
||||
> Adapter pattern lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -671,9 +609,7 @@ class AsianLion implements Lion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And hunter expects any implementation of `Lion` interface to hunt.
|
||||
|
||||
```php
|
||||
class Hunter
|
||||
{
|
||||
@@ -711,7 +647,6 @@ class WildDogAdapter implements Lion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And now the `WildDog` can be used in our game using `WildDogAdapter`.
|
||||
|
||||
```php
|
||||
@@ -722,20 +657,17 @@ $hunter = new Hunter();
|
||||
$hunter->hunt($wildDogAdapter);
|
||||
```
|
||||
|
||||
## 🚡 Bridge
|
||||
|
||||
🚡 Bridge
|
||||
------
|
||||
Real world example
|
||||
|
||||
> Consider you have a website with different pages and you are supposed to allow the user to change the theme. What would you do? Create multiple copies of each of the pages for each of the themes or would you just create separate theme and load them based on the user's preferences? Bridge pattern allows you to do the second i.e.
|
||||
|
||||

|
||||
|
||||
In Plain Words
|
||||
|
||||
> Bridge pattern is about preferring composition over inheritance. Implementation details are pushed from a hierarchy to another object with a separate hierarchy.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently"
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -779,9 +711,7 @@ class Careers implements WebPage
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And the separate theme hierarchy
|
||||
|
||||
```php
|
||||
|
||||
interface Theme
|
||||
@@ -811,9 +741,7 @@ class AquaTheme implements Theme
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And both the hierarchies
|
||||
|
||||
```php
|
||||
$darkTheme = new DarkTheme();
|
||||
|
||||
@@ -827,15 +755,12 @@ echo $careers->getContent(); // "Careers page in Dark Black";
|
||||
## 🌿 Composite
|
||||
|
||||
Real world example
|
||||
|
||||
> Every organization is composed of employees. Each of the employees has the same features i.e. has a salary, has some responsibilities, may or may not report to someone, may or may not have some subordinates etc.
|
||||
|
||||
In plain words
|
||||
|
||||
> Composite pattern lets clients treat the individual objects in a uniform manner.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -857,7 +782,7 @@ class Developer implements Employee
|
||||
protected $salary;
|
||||
protected $name;
|
||||
protected $roles;
|
||||
|
||||
|
||||
public function __construct(string $name, float $salary)
|
||||
{
|
||||
$this->name = $name;
|
||||
@@ -959,18 +884,17 @@ $organization->addEmployee($jane);
|
||||
echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 27000
|
||||
```
|
||||
|
||||
## ☕ Decorator
|
||||
☕ Decorator
|
||||
-------------
|
||||
|
||||
Real world example
|
||||
|
||||
> Imagine you run a car service shop offering multiple services. Now how do you calculate the bill to be charged? You pick one service and dynamically keep adding to it the prices for the provided services till you get the final cost. Here each type of service is a decorator.
|
||||
|
||||
In plain words
|
||||
|
||||
> Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -997,9 +921,7 @@ class SimpleCoffee implements Coffee
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We want to make the code extensible to allow options to modify it if required. Lets make some add-ons (decorators)
|
||||
|
||||
```php
|
||||
class MilkCoffee implements Coffee
|
||||
{
|
||||
@@ -1082,18 +1004,16 @@ echo $someCoffee->getCost(); // 20
|
||||
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla
|
||||
```
|
||||
|
||||
## 📦 Facade
|
||||
📦 Facade
|
||||
----------------
|
||||
|
||||
Real world example
|
||||
|
||||
> How do you turn on the computer? "Hit the power button" you say! That is what you believe because you are using a simple interface that computer provides on the outside, internally it has to do a lot of stuff to make it happen. This simple interface to the complex subsystem is a facade.
|
||||
|
||||
In plain words
|
||||
|
||||
> Facade pattern provides a simplified interface to a complex subsystem.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> A facade is an object that provides a simplified interface to a larger body of code, such as a class library.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -1139,9 +1059,7 @@ class Computer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here we have the facade
|
||||
|
||||
```php
|
||||
class ComputerFacade
|
||||
{
|
||||
@@ -1168,27 +1086,23 @@ class ComputerFacade
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now to use the facade
|
||||
|
||||
```php
|
||||
$computer = new ComputerFacade(new Computer());
|
||||
$computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
|
||||
$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz
|
||||
```
|
||||
|
||||
## 🍃 Flyweight
|
||||
🍃 Flyweight
|
||||
---------
|
||||
|
||||
Real world example
|
||||
|
||||
> Did you ever have fresh tea from some stall? They often make more than one cup that you demanded and save the rest for any other customer so to save the resources e.g. gas etc. Flyweight pattern is all about that i.e. sharing.
|
||||
|
||||
In plain words
|
||||
|
||||
> It is used to minimize memory usage or computational expenses by sharing as much as possible with similar objects.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.
|
||||
|
||||
**Programmatic example**
|
||||
@@ -1244,7 +1158,6 @@ class TeaShop
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And it can be used as below
|
||||
|
||||
```php
|
||||
@@ -1262,17 +1175,13 @@ $shop->serve();
|
||||
```
|
||||
|
||||
## 🎱 Proxy
|
||||
|
||||
Real world example
|
||||
|
||||
> Have you ever used an access card to go through a door? There are multiple options to open that door i.e. it can be opened either using access card or by pressing a button that bypasses the security. The door's main functionality is to open but there is a proxy added on top of it to add some functionality. Let me better explain it using the code example below.
|
||||
|
||||
In plain words
|
||||
|
||||
> Using the proxy pattern, a class represents the functionality of another class.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -1299,9 +1208,7 @@ class LabDoor implements Door
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have a proxy to secure any doors that we want
|
||||
|
||||
```php
|
||||
class SecuredDoor
|
||||
{
|
||||
@@ -1332,9 +1239,7 @@ class SecuredDoor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And here is how it can be used
|
||||
|
||||
```php
|
||||
$door = new SecuredDoor(new LabDoor());
|
||||
$door->open('invalid'); // Big no! It ain't possible.
|
||||
@@ -1342,44 +1247,38 @@ $door->open('invalid'); // Big no! It ain't possible.
|
||||
$door->open('$ecr@t'); // Opening lab door
|
||||
$door->close(); // Closing lab door
|
||||
```
|
||||
|
||||
Yet another example would be some sort of data-mapper implementation. For example, I recently made an ODM (Object Data Mapper) for MongoDB using this pattern where I wrote a proxy around mongo classes while utilizing the magic method `__call()`. All the method calls were proxied to the original mongo class and result retrieved was returned as it is but in case of `find` or `findOne` data was mapped to the required class objects and the object was returned instead of `Cursor`.
|
||||
|
||||
## Behavioral Design Patterns
|
||||
|
||||
In plain words
|
||||
|
||||
> It is concerned with assignment of responsibilities between the objects. What makes them different from structural patterns is they don't just specify the structure but also outline the patterns for message passing/communication between them. Or in other words, they assist in answering "How to run a behavior in software component?"
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.
|
||||
|
||||
There are 10 types of behavioral design patterns
|
||||
|
||||
- [Chain of Responsibility](#-chain-of-responsibility)
|
||||
- [Command](#-command)
|
||||
- [Iterator](#-iterator)
|
||||
- [Mediator](#-mediator)
|
||||
- [Memento](#-memento)
|
||||
- [Observer](#-observer)
|
||||
- [Visitor](#-visitor)
|
||||
- [Strategy](#-strategy)
|
||||
- [State](#-state)
|
||||
- [Template Method](#-template-method)
|
||||
* [Chain of Responsibility](#-chain-of-responsibility)
|
||||
* [Command](#-command)
|
||||
* [Iterator](#-iterator)
|
||||
* [Mediator](#-mediator)
|
||||
* [Memento](#-memento)
|
||||
* [Observer](#-observer)
|
||||
* [Visitor](#-visitor)
|
||||
* [Strategy](#-strategy)
|
||||
* [State](#-state)
|
||||
* [Template Method](#-template-method)
|
||||
|
||||
## 🔗 Chain of Responsibility
|
||||
|
||||
Real world example
|
||||
|
||||
> For example, you have three payment methods (`A`, `B` and `C`) setup in your account; each having a different amount in it. `A` has 100 USD, `B` has 300 USD and `C` having 1000 USD and the preference for payments is chosen as `A` then `B` then `C`. You try to purchase something that is worth 210 USD. Using Chain of Responsibility, first of all account `A` will be checked if it can make the purchase, if yes purchase will be made and the chain will be broken. If not, request will move forward to account `B` checking for amount if yes chain will be broken otherwise the request will keep forwarding till it finds the suitable handler. Here `A`, `B` and `C` are links of the chain and the whole phenomenon is Chain of Responsibility.
|
||||
|
||||
In plain words
|
||||
|
||||
> It helps building a chain of objects. Request enters from one end and keeps going from object to object till it finds the suitable handler.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -1473,25 +1372,22 @@ $bank->pay(259);
|
||||
// Paid 259 using Bitcoin!
|
||||
```
|
||||
|
||||
## 👮 Command
|
||||
👮 Command
|
||||
-------
|
||||
|
||||
Real world example
|
||||
|
||||
> A generic example would be you ordering food at a restaurant. You (i.e. `Client`) ask the waiter (i.e. `Invoker`) to bring some food (i.e. `Command`) and waiter simply forwards the request to Chef (i.e. `Receiver`) who has the knowledge of what and how to cook.
|
||||
> Another example would be you (i.e. `Client`) switching on (i.e. `Command`) the television (i.e. `Receiver`) using a remote control (`Invoker`).
|
||||
|
||||
In plain words
|
||||
|
||||
> Allows you to encapsulate actions in objects. The key idea behind this pattern is to provide the means to decouple client from receiver.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
First of all we have the receiver that has the implementation of every action that could be performed
|
||||
|
||||
```php
|
||||
// Receiver
|
||||
class Bulb
|
||||
@@ -1507,9 +1403,7 @@ class Bulb
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
then we have an interface that each of the commands are going to implement and then we have a set of commands
|
||||
|
||||
```php
|
||||
interface Command
|
||||
{
|
||||
@@ -1569,9 +1463,7 @@ class TurnOff implements Command
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have an `Invoker` with whom the client will interact to process any commands
|
||||
|
||||
```php
|
||||
// Invoker
|
||||
class RemoteControl
|
||||
@@ -1582,9 +1474,7 @@ class RemoteControl
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally let's see how we can use it in our client
|
||||
|
||||
```php
|
||||
$bulb = new Bulb();
|
||||
|
||||
@@ -1598,18 +1488,16 @@ $remote->submit($turnOff); // Darkness!
|
||||
|
||||
Command pattern can also be used to implement a transaction based system. Where you keep maintaining the history of commands as soon as you execute them. If the final command is successfully executed, all good otherwise just iterate through the history and keep executing the `undo` on all the executed commands.
|
||||
|
||||
## ➿ Iterator
|
||||
➿ Iterator
|
||||
--------
|
||||
|
||||
Real world example
|
||||
|
||||
> An old radio set will be a good example of iterator, where user could start at some channel and then use next or previous buttons to go through the respective channels. Or take an example of MP3 player or a TV set where you could press the next and previous buttons to go through the consecutive channels or in other words they all provide an interface to iterate through the respective channels, songs or radio stations.
|
||||
> An old radio set will be a good example of iterator, where user could start at some channel and then use next or previous buttons to go through the respective channels. Or take an example of MP3 player or a TV set where you could press the next and previous buttons to go through the consecutive channels or in other words they all provide an interface to iterate through the respective channels, songs or radio stations.
|
||||
|
||||
In plain words
|
||||
|
||||
> It presents a way to access the elements of an object without exposing the underlying presentation.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.
|
||||
|
||||
**Programmatic example**
|
||||
@@ -1632,7 +1520,6 @@ class RadioStation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have our iterator
|
||||
|
||||
```php
|
||||
@@ -1691,9 +1578,7 @@ class StationList implements Countable, Iterator
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
$stationList = new StationList();
|
||||
|
||||
@@ -1709,18 +1594,16 @@ foreach($stationList as $station) {
|
||||
$stationList->removeStation(new RadioStation(89)); // Will remove station 89
|
||||
```
|
||||
|
||||
## 👽 Mediator
|
||||
👽 Mediator
|
||||
--------
|
||||
|
||||
Real world example
|
||||
|
||||
> A general example would be when you talk to someone on your mobile phone, there is a network provider sitting between you and them and your conversation goes through it instead of being directly sent. In this case network provider is mediator.
|
||||
|
||||
In plain words
|
||||
|
||||
> Mediator pattern adds a third party object (called mediator) to control the interaction between two objects (called colleagues). It helps reduce the coupling between the classes communicating with each other. Because now they don't need to have the knowledge of each other's implementation.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -1730,7 +1613,7 @@ Here is the simplest example of a chat room (i.e. mediator) with users (i.e. col
|
||||
First of all, we have the mediator i.e. the chat room
|
||||
|
||||
```php
|
||||
interface ChatRoomMediator
|
||||
interface ChatRoomMediator
|
||||
{
|
||||
public function showMessage(User $user, string $message);
|
||||
}
|
||||
@@ -1749,7 +1632,6 @@ class ChatRoom implements ChatRoomMediator
|
||||
```
|
||||
|
||||
Then we have our users i.e. colleagues
|
||||
|
||||
```php
|
||||
class User {
|
||||
protected $name;
|
||||
@@ -1769,9 +1651,7 @@ class User {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And the usage
|
||||
|
||||
```php
|
||||
$mediator = new ChatRoom();
|
||||
|
||||
@@ -1786,18 +1666,15 @@ $jane->send('Hey!');
|
||||
// Feb 14, 10:58 [Jane]: Hey!
|
||||
```
|
||||
|
||||
## 💾 Memento
|
||||
|
||||
💾 Memento
|
||||
-------
|
||||
Real world example
|
||||
|
||||
> Take the example of calculator (i.e. originator), where whenever you perform some calculation the last calculation is saved in memory (i.e. memento) so that you can get back to it and maybe get it restored using some action buttons (i.e. caretaker).
|
||||
|
||||
In plain words
|
||||
|
||||
> Memento pattern is about capturing and storing the current state of an object in a manner that it can be restored later on in a smooth manner.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).
|
||||
|
||||
Usually useful when you need to provide some sort of undo functionality.
|
||||
@@ -1878,24 +1755,20 @@ $editor->restore($saved);
|
||||
$editor->getContent(); // This is the first sentence. This is second.
|
||||
```
|
||||
|
||||
## 😎 Observer
|
||||
|
||||
😎 Observer
|
||||
--------
|
||||
Real world example
|
||||
|
||||
> A good example would be the job seekers where they subscribe to some job posting site and they are notified whenever there is a matching job opportunity.
|
||||
> A good example would be the job seekers where they subscribe to some job posting site and they are notified whenever there is a matching job opportunity.
|
||||
|
||||
In plain words
|
||||
|
||||
> Defines a dependency between objects so that whenever an object changes its state, all its dependents are notified.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
|
||||
|
||||
**Programmatic example**
|
||||
|
||||
Translating our example from above. First of all we have job seekers that need to be notified for a job posting
|
||||
|
||||
```php
|
||||
class JobPost
|
||||
{
|
||||
@@ -1928,9 +1801,7 @@ class JobSeeker implements Observer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have our job postings to which the job seekers will subscribe
|
||||
|
||||
```php
|
||||
class EmploymentAgency implements Observable
|
||||
{
|
||||
@@ -1954,9 +1825,7 @@ class EmploymentAgency implements Observable
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then it can be used as
|
||||
|
||||
```php
|
||||
// Create subscribers
|
||||
$johnDoe = new JobSeeker('John Doe');
|
||||
@@ -1975,18 +1844,15 @@ $jobPostings->addJob(new JobPost('Software Engineer'));
|
||||
// Hi Jane Doe! New job posted: Software Engineer
|
||||
```
|
||||
|
||||
## 🏃 Visitor
|
||||
|
||||
🏃 Visitor
|
||||
-------
|
||||
Real world example
|
||||
|
||||
> Consider someone visiting Dubai. They just need a way (i.e. visa) to enter Dubai. After arrival, they can come and visit any place in Dubai on their own without having to ask for permission or to do some leg work in order to visit any place here; just let them know of a place and they can visit it. Visitor pattern lets you do just that, it helps you add places to visit so that they can visit as much as they can without having to do any legwork.
|
||||
|
||||
In plain words
|
||||
|
||||
> Visitor pattern lets you add further operations to objects without having to modify them.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.
|
||||
|
||||
**Programmatic example**
|
||||
@@ -2008,9 +1874,7 @@ interface AnimalOperation
|
||||
public function visitDolphin(Dolphin $dolphin);
|
||||
}
|
||||
```
|
||||
|
||||
Then we have our implementations for the animals
|
||||
|
||||
```php
|
||||
class Monkey implements Animal
|
||||
{
|
||||
@@ -2051,9 +1915,7 @@ class Dolphin implements Animal
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's implement our visitor
|
||||
|
||||
```php
|
||||
class Speak implements AnimalOperation
|
||||
{
|
||||
@@ -2075,7 +1937,6 @@ class Speak implements AnimalOperation
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
$monkey = new Monkey();
|
||||
$lion = new Lion();
|
||||
@@ -2083,11 +1944,10 @@ $dolphin = new Dolphin();
|
||||
|
||||
$speak = new Speak();
|
||||
|
||||
$monkey->accept($speak); // Ooh oo aa aa!
|
||||
$monkey->accept($speak); // Ooh oo aa aa!
|
||||
$lion->accept($speak); // Roaaar!
|
||||
$dolphin->accept($speak); // Tuut tutt tuutt!
|
||||
```
|
||||
|
||||
We could have done this simply by having an inheritance hierarchy for the animals but then we would have to modify the animals whenever we would have to add new actions to animals. But now we will not have to change them. For example, let's say we are asked to add the jump behavior to the animals, we can simply add that by creating a new visitor i.e.
|
||||
|
||||
```php
|
||||
@@ -2109,9 +1969,7 @@ class Jump implements AnimalOperation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And for the usage
|
||||
|
||||
```php
|
||||
$jump = new Jump();
|
||||
|
||||
@@ -2125,18 +1983,16 @@ $dolphin->accept($speak); // Tuut tutt tuutt!
|
||||
$dolphin->accept($jump); // Walked on water a little and disappeared
|
||||
```
|
||||
|
||||
## 💡 Strategy
|
||||
💡 Strategy
|
||||
--------
|
||||
|
||||
Real world example
|
||||
|
||||
> Consider the example of sorting, we implemented bubble sort but the data started to grow and bubble sort started getting very slow. In order to tackle this we implemented Quick sort. But now although the quick sort algorithm was doing better for large datasets, it was very slow for smaller datasets. In order to handle this we implemented a strategy where for small datasets, bubble sort will be used and for larger, quick sort.
|
||||
|
||||
In plain words
|
||||
|
||||
> Strategy pattern allows you to switch the algorithm or strategy based upon the situation.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioural software design pattern that enables an algorithm's behavior to be selected at runtime.
|
||||
|
||||
**Programmatic example**
|
||||
@@ -2173,7 +2029,6 @@ class QuickSortStrategy implements SortStrategy
|
||||
```
|
||||
|
||||
And then we have our client that is going to use any strategy
|
||||
|
||||
```php
|
||||
class Sorter
|
||||
{
|
||||
@@ -2190,9 +2045,7 @@ class Sorter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And it can be used as
|
||||
|
||||
```php
|
||||
$dataset = [1, 5, 4, 3, 2, 8];
|
||||
|
||||
@@ -2203,18 +2056,15 @@ $sorter = new Sorter(new QuickSortStrategy());
|
||||
$sorter->sort($dataset); // Output : Sorting using quick sort
|
||||
```
|
||||
|
||||
## 💢 State
|
||||
|
||||
💢 State
|
||||
-----
|
||||
Real world example
|
||||
|
||||
> Imagine you are using some drawing application, you choose the paint brush to draw. Now the brush changes its behavior based on the selected color i.e. if you have chosen red color it will draw in red, if blue then it will be in blue etc.
|
||||
> Imagine you are using some drawing application, you choose the paint brush to draw. Now the brush changes its behavior based on the selected color i.e. if you have chosen red color it will draw in red, if blue then it will be in blue etc.
|
||||
|
||||
In plain words
|
||||
|
||||
> It lets you change the behavior of a class when the state changes.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> The state pattern is a behavioral software design pattern that implements a state machine in an object-oriented way. With the state pattern, a state machine is implemented by implementing each individual state as a derived class of the state pattern interface, and implementing state transitions by invoking methods defined by the pattern's superclass.
|
||||
> The state pattern can be interpreted as a strategy pattern which is able to switch the current strategy through invocations of methods defined in the pattern's interface.
|
||||
|
||||
@@ -2254,9 +2104,7 @@ class DefaultText implements WritingState
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then we have our editor
|
||||
|
||||
```php
|
||||
class TextEditor
|
||||
{
|
||||
@@ -2278,9 +2126,7 @@ class TextEditor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
$editor = new TextEditor(new DefaultText());
|
||||
|
||||
@@ -2304,12 +2150,11 @@ $editor->type('Fifth line');
|
||||
// fifth line
|
||||
```
|
||||
|
||||
## 📒 Template Method
|
||||
📒 Template Method
|
||||
---------------
|
||||
|
||||
Real world example
|
||||
|
||||
> Suppose we are getting some house built. The steps for building might look like
|
||||
>
|
||||
> - Prepare the base of house
|
||||
> - Build the walls
|
||||
> - Add roof
|
||||
@@ -2318,11 +2163,9 @@ Real world example
|
||||
> The order of these steps could never be changed i.e. you can't build the roof before building the walls etc but each of the steps could be modified for example walls can be made of wood or polyester or stone.
|
||||
|
||||
In plain words
|
||||
|
||||
> Template method defines the skeleton of how a certain algorithm could be performed, but defers the implementation of those steps to the children classes.
|
||||
|
||||
Wikipedia says
|
||||
|
||||
> In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
|
||||
|
||||
**Programmatic Example**
|
||||
@@ -2330,7 +2173,6 @@ Wikipedia says
|
||||
Imagine we have a build tool that helps us test, lint, build, generate build reports (i.e. code coverage reports, linting report etc) and deploy our app on the test server.
|
||||
|
||||
First of all we have our base class that specifies the skeleton for the build algorithm
|
||||
|
||||
```php
|
||||
abstract class Builder
|
||||
{
|
||||
@@ -2400,7 +2242,6 @@ class IosBuilder extends Builder
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then it can be used as
|
||||
|
||||
```php
|
||||
2
content/guides/dhcp-in-one-picture.md
Normal file
@@ -0,0 +1,2 @@
|
||||
[](/guides/dhcp.png)
|
||||
|
||||