Compare commits

..

126 Commits

Author SHA1 Message Date
Arik Chakma
3771326cd8 chore: chart 2023-06-22 02:01:28 +06:00
Aaryan Dewan
ff0e10c16c Correct grammar (#4095)
Changed 'al' to 'all'
2023-06-21 20:40:56 +06:00
roadmap bot
ec165d4a78 chore: add resource under devops:networking-protocols 2023-06-20 22:03:44 +01:00
Arik Chakma
afe718ee09 Allow marking roadmaps and best practices as favorites (#4087)
* chore: favorite icon

* fix: hero progress mark favorit

* chore: mark favorite

* fix: mouse overflow

* fix: popup redirect

* Update favorites on homepage

* Refactor favorite logic

* Change icon location

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2023-06-20 21:50:18 +01:00
Ritik Ranjan
4aca01a98d Fix spelling mistake (#4088) 2023-06-20 18:24:48 +01:00
Kamran Ahmed
140282f1ff Update devops roadmap link 2023-06-20 17:31:31 +01:00
roadmap bot
4d38d19e4f chore: add resource under aspnet-core:basics-of-aspnet-core:filters-and-attributes 2023-06-20 15:16:05 +01:00
roadmap bot
5e39417a64 chore: add resource under cyber-security:security-skills-and-knowledge:common-exploit-frameworks 2023-06-20 15:15:57 +01:00
roadmap bot
03ec7ebcd9 chore: add resource under javascript:javascript-variables:scopes 2023-06-20 15:15:44 +01:00
roadmap bot
fbb6def555 chore: add resource under computer-science:pick-a-language:c-plus-plus 2023-06-20 15:15:24 +01:00
roadmap bot
ae9e30eb73 chore: add resource under mongodb:mongodb-basics:sql-vs-nosql 2023-06-20 15:15:03 +01:00
roadmap bot
9e89c6946b chore: add resource under ux-design:human-decision-making 2023-06-20 15:14:48 +01:00
Arik Chakma
6ff83d0797 Merge pull request #3766 from jensrott/fix-typo-playwright
Fixed typo in the word tutorial
2023-06-20 00:53:01 +06:00
Arik Chakma
5ff131ae29 Merge pull request #3873 from the-land-mine/master
fix: Correct syntax error in Promise initialization example by adding space
2023-06-20 00:51:52 +06:00
Arik Chakma
e80f88ef2c Merge pull request #4049 from arzkar/issue4044_fix
fix: typo: mor -> more
2023-06-20 00:49:50 +06:00
Arik Chakma
cff01c151b Merge pull request #4080 from JustLolo/master
The external link is broken
2023-06-20 00:48:48 +06:00
Arik Chakma
6ca85a41a2 Merge pull request #4081 from johan456789/master
fix URL link
2023-06-20 00:46:32 +06:00
JustLolo
1630b493b1 External link is broken, fixed 2023-06-19 06:41:26 -05:00
Tsung-Han Yu
518ece3cab fix URL link 2023-06-19 10:34:37 +08:00
JustLolo
aba2fd1d35 External broken link, Youtube is showing:
`This video isn't available anymore`
2023-06-18 18:11:38 -05:00
Arik Chakma
fcd68568c2 Merge pull request #4076 from ShawnNectar/patch-1
[Issue] #4075 Wrong link assigned
2023-06-18 22:11:26 +06:00
Shawn Nectar
1b5e9ffe0d [Issue] #4075 Wrong link assigned 2023-06-18 12:58:33 -03:00
Kamran Ahmed
b3c3e44ba2 Update shortcut for marking as skipped 2023-06-17 23:13:59 +01:00
Kamran Ahmed
67b49d3f87 Remove new badges from old roadmaps 2023-06-17 16:17:42 +01:00
roadmap bot
0d3e1d31bb chore: add resource under aspnet-core:orm:entity-framework-core:change-tracker-api 2023-06-17 15:23:52 +01:00
roadmap bot
28a27a1c65 chore: add resource under computer-science:pick-a-language:c 2023-06-17 15:23:36 +01:00
roadmap bot
8c3ea21ef1 chore: add resource under cpp:introduction 2023-06-17 15:22:41 +01:00
roadmap bot
417596db36 chore: add resource under frontend:progressive-web-apps:notifications 2023-06-17 15:22:30 +01:00
roadmap bot
28240162b3 chore: add resource under frontend:build-tools:module-bundlers:esbuild 2023-06-17 15:22:11 +01:00
roadmap bot
6dca357782 chore: add resource under blockchain:blockchain-general-knowledge:blockchain-forking 2023-06-17 15:21:57 +01:00
roadmap bot
d1fe06a4e9 chore: add resource under flutter:widgets:responsive-widgets 2023-06-17 15:20:28 +01:00
roadmap bot
97cba5681b chore: add resource under full-stack:html 2023-06-17 15:20:15 +01:00
roadmap bot
715d2ba62b chore: add resource under golang:go-advanced:working-with-json 2023-06-17 15:19:54 +01:00
Kamran Ahmed
32673c21fb Add shortcuts for progress tracking 2023-06-17 15:19:24 +01:00
roadmap bot
f0c47705cb chore: add resource under nodejs:nodejs-command-line-apps:command-line-args 2023-06-17 15:17:18 +01:00
roadmap bot
612b91e05f chore: add resource under full-stack:nodejs 2023-06-17 15:17:08 +01:00
roadmap bot
b4cce42844 chore: add resource under devops:serverless:azure-functions 2023-06-17 15:16:41 +01:00
roadmap bot
2c2d57ecab chore: add resource under cpp:functions 2023-06-17 15:16:36 +01:00
roadmap bot
d05374ca68 chore: add resource under ux-design:human-decision-making:ux-buzzwords:nudge-theory 2023-06-17 15:16:14 +01:00
roadmap bot
b5c02a9aff chore: add resource under cyber-security:basic-it-skills:popular-suites:icloud 2023-06-17 15:16:04 +01:00
roadmap bot
1e3568a1c4 chore: add resource under cyber-security:networking-knowledge:understand-the-terminology:dns 2023-06-17 15:15:44 +01:00
Arik Chakma
bdeebbc9cc chore: linkedin login functionality (#4072) 2023-06-17 12:31:33 +01:00
Kamran Ahmed
510e6fd273 Update youtube banner 2023-06-17 11:49:15 +01:00
Kamran Ahmed
2ca98bbb10 Show resource progress on best practices 2023-06-17 11:47:25 +01:00
Kamran Ahmed
49cff0c22c Add progress loading on roadmap pages 2023-06-17 05:43:50 +01:00
Kamran Ahmed
943bf41dc5 Fix duplicate nodes in frontend roadmap 2023-06-17 04:30:58 +01:00
Umair Khan
6c9ba75906 Fix KodeKloud spelling (#4066)
* Update devops.json 

kodekloud spelling correction

* Update src/data/roadmaps/devops/devops.json

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2023-06-15 22:17:53 +01:00
Kamran Ahmed
70976ee42a Add roadcard as protected route 2023-06-15 14:51:52 +01:00
roadmap bot
5848698abf chore: add resource under python:python-advanced-topics:lambdas 2023-06-15 02:27:28 +01:00
roadmap bot
29dd1eb21f chore: add resource under python:python-advanced-topics:decorators 2023-06-15 02:27:17 +01:00
roadmap bot
ebe6d3c6e4 chore: add resource under system-design:latency-vs-throughput 2023-06-15 02:27:06 +01:00
roadmap bot
425bfea265 chore: add resource under system-design:performance-vs-scalability 2023-06-15 02:26:57 +01:00
roadmap bot
c58efe8d00 chore: add resource under python:python-advanced-topics:oop:classes 2023-06-15 02:26:39 +01:00
Kamran Ahmed
955d04e532 UI changes on road cards 2023-06-14 20:58:15 +01:00
Kamran Ahmed
0031a9c6ba Remove preact-compat 2023-06-14 20:42:32 +01:00
Kamran Ahmed
8fb778337d Add support for roadcards 2023-06-14 20:42:07 +01:00
Kamran Ahmed
a48d39a863 Update animation of progress switcher 2023-06-14 15:02:53 +01:00
Kamran Ahmed
36b2a8f2d7 Progress container update 2023-06-14 14:46:10 +01:00
Kamran Ahmed
00e9d44ba9 Remove client:only from favorite roadmaps 2023-06-14 13:54:54 +01:00
Kamran Ahmed
62b068a94a Fix jitter on homepage 2023-06-14 13:51:29 +01:00
Kamran Ahmed
af926002e9 feat: show user's progress on homepage (#4058)
* Add custom client:authenticated directive

* Update 100-installing-a-local-cluster.md

fixed typo for ubuntu in 100-installing-a-local-cluster.md

* Animate progress on the homescreen

* Show progress on homepage

* Update progress list UI

* Remove sponsor call from non-required pages

* Resolve merge conflicts

* Change height of hero container

---------

Co-authored-by: kanhaya kumar yadav <kanhaya.workspace@gmail.com>
2023-06-14 13:12:52 +01:00
roadmap bot
0612f9c44f chore: add resource under docker:introduction:what-are-containers 2023-06-14 03:47:19 +01:00
roadmap bot
fbf545c2ed chore: add resource under cyber-security:security-skills-and-knowledge:common-distros-for-hacking:parrot-os 2023-06-14 03:47:04 +01:00
roadmap bot
c7ef97cb4f chore: add resource under react:rendering:refs 2023-06-14 03:46:43 +01:00
roadmap bot
564f48540e chore: add resource under react:rendering:render-props 2023-06-14 03:46:29 +01:00
roadmap bot
52e729d212 chore: add resource under prompt-engineering:prompting-introduction 2023-06-14 03:45:32 +01:00
roadmap bot
bdfa7606dd chore: add resource under devops:live-in-terminal:scripting:powershell 2023-06-14 03:45:21 +01:00
roadmap bot
056e0e8e3a chore: add resource under react:rendering:lists-and-keys 2023-06-14 03:45:11 +01:00
roadmap bot
879ba258b2 chore: add resource under cyber-security:basic-it-skills:connection-types:wifi 2023-06-14 03:44:40 +01:00
Kamran Ahmed
3d62d2689f Animate progress on the homescreen 2023-06-14 02:09:09 +01:00
Arbaaz Laskar
3b7a9ca5cd fix: typo: mor -> more 2023-06-14 00:06:37 +05:30
Arik Chakma
ac892d2868 Merge pull request #4047 from kanhayaKy/patch-1
fix: ubuntu type
2023-06-13 17:57:19 +06:00
roadmap bot
19bde7bb2f chore: add resource under cyber-security:security-skills-and-knowledge:forensics 2023-06-13 11:38:12 +01:00
roadmap bot
419b1872b8 chore: add resource under javascript:javascript-asynchronous-javascript:callbacks 2023-06-13 11:37:50 +01:00
roadmap bot
bbeb4ee279 chore: add resource under devops:live-in-terminal:scripting:bash-scripting 2023-06-13 11:37:42 +01:00
roadmap bot
f2ca7d9140 chore: add resource under backend:relational-databases:mysql 2023-06-13 11:37:12 +01:00
roadmap bot
70b95c6ad1 chore: add resource under javascript:javascript-asynchronous-javascript:callbacks:callback-hell 2023-06-13 11:36:49 +01:00
roadmap bot
5a3f621093 chore: add resource under javascript:javascript-loops-iterations:break-continue:labeled-statements 2023-06-13 11:36:41 +01:00
roadmap bot
631eb380fc chore: add resource under cpp:pointers-and-references:smart-pointers:weak-ptr 2023-06-13 11:36:31 +01:00
roadmap bot
cb9778ba15 chore: add resource under cyber-security:basic-it-skills:os-independent-troubleshooting 2023-06-13 11:36:20 +01:00
roadmap bot
38106a8199 chore: add resource under typescript:typescript-types:type-assertions:as-type 2023-06-13 11:35:46 +01:00
roadmap bot
226e94857b chore: add resource under aspnet-core:basics-of-csharp:csharp 2023-06-13 11:35:31 +01:00
roadmap bot
f94c701657 chore: add resource under computer-science:pick-a-language:c-plus-plus 2023-06-13 11:35:17 +01:00
roadmap bot
259109cc38 chore: add resource under cyber-security:basic-it-skills 2023-06-13 11:35:04 +01:00
kanhaya kumar yadav
e120df30e3 Update 100-installing-a-local-cluster.md
fixed typo for ubuntu in 100-installing-a-local-cluster.md
2023-06-13 11:46:55 +05:30
Kamran Ahmed
43f351a943 Add progress loading on homepage roadmaps 2023-06-13 03:19:59 +01:00
roadmap bot
502b8e20d5 chore: add resource under computer-science:common-algorithms:graph-algorithms:breadth-first-search 2023-06-11 18:44:23 +01:00
roadmap bot
ff5858f965 chore: add resource under flutter:widgets:inherited-widgets 2023-06-11 18:43:59 +01:00
roadmap bot
8b8ef52d98 chore: add resource under python:python-basics 2023-06-11 18:43:37 +01:00
roadmap bot
7032bc0726 chore: add resource under backend:repo-hosting-services:github 2023-06-11 18:43:29 +01:00
roadmap bot
ba65dec596 chore: add resource under cpp:libraries:poco 2023-06-11 18:42:48 +01:00
roadmap bot
78cf88fbd9 chore: add resource under flutter:design-principles:design-patterns 2023-06-11 02:10:16 +01:00
roadmap bot
93e16d899a chore: add resource under devops:artifcats:nexus 2023-06-11 02:09:49 +01:00
roadmap bot
14060bda94 chore: add resource under javascript:javascript-control-flow:exception-handling:throw-statement 2023-06-11 02:08:21 +01:00
Kamran Ahmed
45b729d708 Update the schema updated date 2023-06-10 20:28:13 +01:00
roadmap bot
9023ea6298 chore: add resource under angular:typescript-basics:union-types 2023-06-10 14:06:00 +01:00
Kamran Ahmed
d29176cf98 Add links to beginner versions 2023-06-10 11:40:24 +01:00
Kamran Ahmed
55989d8480 Add updated devops roadmap pdf 2023-06-10 04:03:38 +01:00
Kamran Ahmed
9c936974c7 Add devops beginner roadmap 2023-06-10 04:02:51 +01:00
Kamran Ahmed
311b4683d0 Rewrite devops roadmap 2023-06-10 04:02:48 +01:00
roadmap bot
bf61697154 chore: add resource under react:hooks:common-hooks 2023-06-09 21:01:31 +01:00
roadmap bot
52818f1e34 chore: add resource under blockchain:blockchain-basics 2023-06-09 21:01:17 +01:00
roadmap bot
174ea05a92 chore: add resource under devops:infrastructure-as-code:kubernetes 2023-06-09 20:59:40 +01:00
roadmap bot
dcb4e06fea chore: add resource under cyber-security:security-skills-and-knowledge:blue-team-read-team-purple-team 2023-06-09 01:53:31 +01:00
roadmap bot
62eb6a4a01 chore: add resource under postgresql-dba:introduction:what-are-relational-databases 2023-06-09 01:53:04 +01:00
roadmap bot
f643f3bd9a chore: add resource under kubernetes:running-applications:deployments 2023-06-09 01:52:10 +01:00
roadmap bot
972370e0e6 chore: add resource under angular:typescript-basics:type-guard 2023-06-09 01:52:01 +01:00
roadmap bot
a6feb72339 chore: add resource under cyber-security:basic-it-skills:connection-types:nfc 2023-06-09 01:51:47 +01:00
roadmap bot
c751706631 chore: add resource under cpp:functions:lambda 2023-06-09 01:51:32 +01:00
roadmap bot
8900324234 chore: add resource under frontend:css:responsive-design-and-media-queries 2023-06-09 01:51:04 +01:00
roadmap bot
f1b880d898 chore: add resource under java:java-advanced-topics:memory-management 2023-06-09 01:50:50 +01:00
roadmap bot
9a285d7470 chore: add resource under cpp:setting-up:code-editors 2023-06-09 01:50:36 +01:00
roadmap bot
15259560e0 chore: add resource under javascript:javascript-control-flow:exception-handling 2023-06-09 01:50:19 +01:00
roadmap bot
d8afa166aa chore: add resource under python:python-basics:variables-and-datatypes 2023-06-08 14:34:57 +01:00
roadmap bot
d39791257e chore: add resource under cpp:introduction:what-is-cpp 2023-06-07 14:48:42 +01:00
Kamran Ahmed
06b7005782 chore: add resource under cyber-security:security-skills-and-knowledge:attack-types:phishing-vishing-whaling-smishing 2023-06-07 11:24:23 +01:00
Kamran Ahmed
bc6c933440 chore: add resource under cyber-security:security-skills-and-knowledge:cryptography 2023-06-07 11:24:05 +01:00
Kamran Ahmed
b965a89db3 chore: add resource under java:java-advanced-topics:garbage-collection 2023-06-07 11:23:32 +01:00
Kamran Ahmed
9b82e327e2 chore: add resource under backend:learn-a-language:java 2023-06-07 11:23:01 +01:00
Kamran Ahmed
5808125d92 chore: add resource under computer-science:pick-a-language:c-plus-plus 2023-06-07 11:22:38 +01:00
Kamran Ahmed
f49fe258aa chore: add resource under cyber-security:basic-it-skills:connection-types:nfc 2023-06-07 11:22:05 +01:00
Kamran Ahmed
08df9e8c33 chore: add resource under cyber-security:extras:certifications:beginner-certifications:comptia-aplus 2023-06-07 11:21:46 +01:00
Kamran Ahmed
56e388edd8 chore: add resource under backend:apis:authentication:openid 2023-06-07 11:21:08 +01:00
Kamran Ahmed
ded75c7af1 chore: add resource under golang:go-basics:structs 2023-06-07 11:20:44 +01:00
Edwin Manual
b6c8260faf fix: Correct syntax error in Promise initialization example by adding space 2023-04-29 07:27:34 +05:30
Jens Rottiers
03f69c02c1 Fixed typo in the word tutorial 2023-04-06 10:08:45 +02:00
163 changed files with 3392 additions and 2018 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.idea
# build output
dist/
.output/

View File

@@ -5,6 +5,7 @@ import tailwind from '@astrojs/tailwind';
import compress from 'astro-compress';
import { defineConfig } from 'astro/config';
import rehypeExternalLinks from 'rehype-external-links';
import { fileURLToPath } from 'node:url';
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
// https://astro.build/config
@@ -45,6 +46,22 @@ export default defineConfig({
format: 'file',
},
integrations: [
{
name: 'client-authenticated',
hooks: {
'astro:config:setup'(options) {
options.addClientDirective({
name: 'authenticated',
entrypoint: fileURLToPath(
new URL(
'./src/directives/client-authenticated.mjs',
import.meta.url
)
),
});
},
},
},
tailwind({
config: {
applyBaseStyles: false,

View File

@@ -26,8 +26,10 @@
"@astrojs/tailwind": "^3.1.3",
"@fingerprintjs/fingerprintjs": "^3.4.1",
"@nanostores/preact": "^0.5.0",
"astro": "^2.5.7",
"astro-compress": "^1.1.46",
"astro": "^2.6.3",
"astro-compress": "^1.1.47",
"chart.js": "^4.3.0",
"chartjs-plugin-datalabels": "^2.2.0",
"jose": "^4.14.4",
"js-cookie": "^3.0.5",
"nanostores": "^0.9.1",
@@ -39,7 +41,7 @@
"tailwindcss": "^3.3.2"
},
"devDependencies": {
"@playwright/test": "^1.34.3",
"@playwright/test": "^1.35.0",
"@tailwindcss/typography": "^0.5.9",
"@types/js-cookie": "^3.0.3",
"csv-parser": "^3.0.0",

1829
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -30,9 +30,9 @@ Roadmaps are now interactive, you can click the nodes to read more about the top
Here is the list of available roadmaps with more being actively worked upon.
- [Frontend Roadmap](https://roadmap.sh/frontend)
- [Frontend Roadmap](https://roadmap.sh/frontend) / [Frontend Beginner Roadmap](https://roadmap.sh/frontend?r=frontend-beginner)
- [Backend Roadmap](https://roadmap.sh/backend)
- [DevOps Roadmap](https://roadmap.sh/devops)
- [DevOps Roadmap](https://roadmap.sh/devops) / [DevOps Beginner Roadmap](https://roadmap.sh/devops?r=devops-beginner)
- [Full Stack Roadmap](https://roadmap.sh/full-stack)
- [Computer Science Roadmap](https://roadmap.sh/computer-science)
- [QA Roadmap](https://roadmap.sh/qa)

View File

@@ -9,39 +9,52 @@ export interface Props {
}
const sidebarLinks = [
{
href: '/account',
title: 'Activity',
id: 'activity',
icon: {
glyph: 'analytics',
classes: 'h-3 w-4',
}
{
href: '/account',
title: 'Activity',
id: 'activity',
isNew: false,
icon: {
glyph: 'analytics',
classes: 'h-3 w-4',
},
{
href: '/account/update-profile',
title: 'Profile',
id: 'profile',
icon: {
glyph: 'user',
classes: 'h-4 w-4',
}
},
{
href: '/account/road-card',
title: 'Card',
id: 'road-card',
isNew: true,
icon: {
glyph: 'badge',
classes: 'h-4 w-4',
},
{
href: '/account/update-password',
title: 'Security',
id: 'change-password',
icon: {
glyph: 'security',
classes: 'h-4 w-4'
}
},
{
href: '/account/update-profile',
title: 'Profile',
id: 'profile',
isNew: false,
icon: {
glyph: 'user',
classes: 'h-4 w-4',
},
},
{
href: '/account/update-password',
title: 'Security',
id: 'change-password',
isNew: false,
icon: {
glyph: 'security',
classes: 'h-4 w-4',
},
},
];
---
<div class='relative mb-5 block border-b p-4 shadow-inner md:hidden'>
<button
class='flex h-10 w-full items-center justify-between rounded-md border bg-white px-2 text-center text-gray-900 text-sm font-medium'
class='flex h-10 w-full items-center justify-between rounded-md border bg-white px-2 text-center text-sm font-medium text-gray-900'
id='settings-menu'
>
{activePageTitle}
@@ -52,42 +65,67 @@ const sidebarLinks = [
class='absolute left-0 right-0 z-10 mt-1 hidden space-y-1.5 bg-white p-2 shadow-lg'
>
{
sidebarLinks.map((sidebarLink) => (
<li>
<a
href={sidebarLink.href}
class={`flex items-center w-full rounded px-3 py-1.5 text-slate-900 hover:bg-slate-200 text-sm ${
activePageId === sidebarLink.id ? 'bg-slate-100' : ''
}`}
>
<AstroIcon icon={sidebarLink.icon.glyph} class={`${sidebarLink.icon.classes} mr-2`} />
sidebarLinks.map((sidebarLink) => {
const isActive = activePageId === sidebarLink.id;
return (
<li>
<a
href={sidebarLink.href}
class={`flex w-full items-center rounded px-3 py-1.5 text-sm text-slate-900 hover:bg-slate-200 ${
isActive ? 'bg-slate-100' : ''
}`}
>
<AstroIcon
icon={sidebarLink.icon.glyph}
class={`${sidebarLink.icon.classes} mr-2`}
/>
{sidebarLink.title}
</a>
</li>
))
</a>
</li>
);
})
}
</ul>
</div>
<div class='container flex min-h-screen items-stretch'>
<!-- Start Desktop Sidebar -->
<aside class='hidden shrink-0 w-44 border-r border-slate-200 py-10 md:block'>
<aside class='hidden w-44 shrink-0 border-r border-slate-200 py-10 md:block'>
<nav>
<ul class='space-y-1'>
{
sidebarLinks.map((sidebarLink) => (
<li>
<a
href={sidebarLink.href}
class={`font-regular flex w-full items-center gap-2 px-2 py-1.5 text-sm border-r-2 ${
activePageId === sidebarLink.id ? 'text-black border-r-black bg-gray-100' : 'text-gray-500 border-r-transparent hover:border-r-gray-300'
}`}
>
<AstroIcon icon={sidebarLink.icon.glyph} class={`${sidebarLink.icon.classes} mr-0`} />
{sidebarLink.title}
</a>
</li>
))
sidebarLinks.map((sidebarLink) => {
const isActive = activePageId === sidebarLink.id;
return (
<li>
<a
href={sidebarLink.href}
class={`font-regular flex w-full items-center border-r-2 px-2 py-1.5 text-sm ${
isActive
? 'border-r-black bg-gray-100 text-black'
: 'border-r-transparent text-gray-500 hover:border-r-gray-300'
}`}
>
<span class='flex flex-grow items-center'>
<AstroIcon
icon={sidebarLink.icon.glyph}
class={`${sidebarLink.icon.classes} mr-2`}
/>
{sidebarLink.title}
</span>
{sidebarLink.isNew && !isActive && (
<span class='relative mr-1 flex items-center'>
<span class='relative rounded-full bg-gray-200 p-1 text-xs' />
<span class='absolute bottom-0 left-0 right-0 top-0 animate-ping rounded-full bg-gray-400 p-1 text-xs' />
</span>
)}
</a>
</li>
);
})
}
</ul>
</nav>

View File

@@ -1,4 +1,6 @@
import { useEffect, useState } from 'preact/hooks';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { Chart as ChartJS, ChartTypeRegistry } from 'chart.js/auto';
import ChartDataLabels from 'chartjs-plugin-datalabels'
import { httpGet } from '../../lib/http';
import { ActivityCounters } from './ActivityCounters';
import { ResourceProgress } from './ResourceProgress';
@@ -50,8 +52,15 @@ type ActivityResponse = {
}[];
};
type ChartLegendItem = {
title: string;
color: string;
}
export function ActivityPage() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [activity, setActivity] = useState<ActivityResponse>();
const [chartLegend, setChartLegend] = useState<ChartLegendItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
async function loadActivity() {
@@ -83,6 +92,56 @@ export function ActivityPage() {
return null;
}
const chartData = useMemo(() => {
return {
labels: [...learningRoadmaps, ...learningBestPractices].map(resource => resource.title),
data: [...learningRoadmaps, ...learningBestPractices].map(resource => resource.done)
}
}, [activity])
useEffect(() => {
let chart: ChartJS<"pie", number[], string> | null = null
const ctx = canvasRef.current?.getContext('2d');
if (!ctx) {
return;
}
if (!chart) {
chart = new ChartJS(ctx, {
type: 'pie',
data: {
labels: chartData.labels,
datasets: [{
data: chartData.data,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: false
},
}
}
});
}
const legendItems = chart?.legend?.legendItems || []
const enrichedLegendItems = legendItems.map((item, index) => {
return {
title: item.text,
color: item.fillStyle?.toString() || ''
}
})
console.log(enrichedLegendItems)
setChartLegend(enrichedLegendItems)
return () => {
chart?.destroy();
};
}, [chartData]);
return (
<>
<ActivityCounters
@@ -91,6 +150,35 @@ export function ActivityPage() {
streak={activity?.streak || { count: 0 }}
/>
<div className="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
<div className="bg-white shadow-lg rounded-2xl p-8">
<h2 className="font-medium">Knowledge Structure</h2>
<div className="grid grid-cols-4 gap-5 mt-6">
<div className="w-full aspect-square flex items-center justify-center h-full">
<canvas
ref={canvasRef}
/>
</div>
<div className="col-span-3">
<div className="flex flex-col gap-1.5 justify-center h-full">
{chartLegend.map((data) => (
<div className="flex items-center gap-2">
<div
style={{
background: `${data.color}`
}}
className="w-3 h-3 rounded-full"
/>
<span className="text-xs text-gray-500">{data.title}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
<div class="mx-0 px-0 py-5 md:-mx-10 md:px-8 md:py-8">
{learningRoadmaps.length === 0 &&
learningBestPractices.length === 0 && <EmptyActivity />}

View File

@@ -1,4 +1,4 @@
import CheckIcon from '../../icons/roadmap.svg';
import RoadmapIcon from '../../icons/roadmap.svg';
export function EmptyActivity() {
return (
@@ -6,7 +6,7 @@ export function EmptyActivity() {
<div class="flex flex-col items-center p-7 text-center">
<img
alt="no roadmaps"
src={CheckIcon}
src={RoadmapIcon}
class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
/>
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2>

View File

@@ -0,0 +1,119 @@
import { useEffect, useState } from 'preact/hooks';
import Cookies from 'js-cookie';
import LinkedIn from '../../icons/linkedin.svg';
import SpinnerIcon from '../../icons/spinner.svg';
import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
import { httpGet } from '../../lib/http';
type LinkedInButtonProps = {};
const LINKEDIN_REDIRECT_AT = 'linkedInRedirectAt';
const LINKEDIN_LAST_PAGE = 'linkedInLastPage';
export function LinkedInButton(props: LinkedInButtonProps) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const icon = isLoading ? SpinnerIcon : LinkedIn;
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const provider = urlParams.get('provider');
if (!code || !state || provider !== 'linkedin') {
return;
}
setIsLoading(true);
httpGet<{ token: string }>(
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
window.location.search
}`
)
.then(({ response, error }) => {
if (!response?.token) {
setError(error?.message || 'Something went wrong.');
setIsLoading(false);
return;
}
let redirectUrl = '/';
const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT);
const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
// If the social redirect is there and less than 30 seconds old
// redirect to the page that user was on before they clicked the github login button
if (linkedInRedirectAt && lastPageBeforeLinkedIn) {
const socialRedirectAtTime = parseInt(linkedInRedirectAt, 10);
const now = Date.now();
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
redirectUrl = lastPageBeforeLinkedIn;
}
}
localStorage.removeItem(LINKEDIN_REDIRECT_AT);
localStorage.removeItem(LINKEDIN_LAST_PAGE);
Cookies.set(TOKEN_COOKIE_NAME, response.token, {
path: '/',
expires: 30,
});
window.location.href = redirectUrl;
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
setIsLoading(false);
});
}, []);
const handleClick = () => {
setIsLoading(true);
httpGet<{ loginUrl: string }>(
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-login`
)
.then(({ response, error }) => {
if (!response?.loginUrl) {
setError(error?.message || 'Something went wrong.');
setIsLoading(false);
return;
}
// For non authentication pages, we want to redirect back to the page
// the user was on before they clicked the social login button
if (!['/login', '/signup'].includes(window.location.pathname)) {
localStorage.setItem(LINKEDIN_REDIRECT_AT, Date.now().toString());
localStorage.setItem(LINKEDIN_LAST_PAGE, window.location.pathname);
}
window.location.href = response.loginUrl;
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
setIsLoading(false);
});
};
return (
<>
<button
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
disabled={isLoading}
onClick={handleClick}
>
<img
src={icon}
alt="Google"
class={`h-[18px] w-[18px] ${isLoading ? 'animate-spin' : ''}`}
/>
Continue with LinkedIn
</button>
{error && (
<p className="mb-2 mt-1 text-sm font-medium text-red-600">{error}</p>
)}
</>
);
}

View File

@@ -4,6 +4,7 @@ import EmailLoginForm from './EmailLoginForm';
import Divider from './Divider.astro';
import { GitHubButton } from './GitHubButton';
import { GoogleButton } from './GoogleButton';
import { LinkedInButton } from './LinkedInButton';
---
<Popup id='login-popup' title='' subtitle=''>
@@ -19,6 +20,7 @@ import { GoogleButton } from './GoogleButton';
<div class='mt-7 flex flex-col gap-2'>
<GitHubButton client:load />
<GoogleButton client:load />
<LinkedInButton client:load />
</div>
<Divider />

View File

@@ -34,6 +34,7 @@ function handleGuest() {
const authenticatedRoutes = [
'/account/update-profile',
'/account/update-password',
'/account/road-card',
'/account',
];

View File

@@ -2,6 +2,7 @@
import Icon from './AstroIcon.astro';
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
import BestPracticeHint from './BestPracticeHint.astro';
import ProgressHelpPopup from './ProgressHelpPopup.astro';
export interface Props {
title: string;
@@ -15,6 +16,7 @@ const isBestPracticeReady = !isUpcoming;
---
<LoginPopup />
<ProgressHelpPopup />
<div class='border-b'>
<div class='container relative py-5 sm:py-12'>

View File

@@ -1,20 +1,10 @@
---
import ResourceProgressStats from './ResourceProgressStats.astro';
export interface Props {
bestPracticeId: string;
}
---
<div class='mt-4 sm:mt-7 border-0 sm:border rounded-md mb-0 sm:-mb-[65px]'>
<!-- Desktop: Roadmap Resources - Alert -->
<div class='hidden sm:flex justify-between px-2 bg-white items-center rounded-md p-1.5'>
<p class='text-sm'>
<span class='text-yellow-900 bg-yellow-200 py-0.5 px-1 text-xs rounded-sm font-medium uppercase mr-0.5'>Tip</span>
Click the best practices for details and resources
</p>
</div>
<!-- Mobile - Roadmap resources alert -->
<p class='block sm:hidden text-sm border border-yellow-500 text-yellow-700 rounded-md py-1.5 px-2 bg-white relative'>
Click the best practices for details and resources
</p>
<ResourceProgressStats />
</div>

View File

@@ -0,0 +1,45 @@
type FavoriteIconProps = {
isFavorite?: boolean;
};
export function FavoriteIcon(props: FavoriteIconProps) {
const { isFavorite } = props;
if (!isFavorite) {
return (
<svg
width="8"
height="10"
viewBox="0 0 8 10"
fill="none"
className="h-3.5 w-3.5"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5ZM5.93682 1.25C6.42732 1.25 6.83382 1.632 6.86382 2.122L7.24932 8.506C7.25216 8.55018 7.24229 8.59425 7.22089 8.63301C7.19949 8.67176 7.16745 8.70359 7.12854 8.72472C7.08964 8.74585 7.0455 8.75542 7.00134 8.75228C6.95718 8.74914 6.91484 8.73343 6.87932 8.707L4.27582 6.783C4.19591 6.72397 4.09917 6.69211 3.99982 6.69211C3.90047 6.69211 3.80373 6.72397 3.72382 6.783L1.11982 8.707C1.0843 8.73343 1.04196 8.74914 0.9978 8.75228C0.953639 8.75542 0.909502 8.74585 0.8706 8.72472C0.831697 8.70359 0.799653 8.67176 0.778252 8.63301C0.756851 8.59425 0.746986 8.55018 0.749822 8.506L1.13632 2.122C1.16632 1.632 1.57232 1.25 2.06282 1.25H5.93682Z"
fill="currentColor"
/>
</svg>
);
}
return (
<svg
width="8"
height="10"
viewBox="0 0 8 10"
className="h-3.5 w-3.5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.93682 0.5H2.06282C1.63546 0.500094 1.22423 0.663195 0.912987 0.956045C0.601741 1.2489 0.413919 1.64944 0.387822 2.076L0.00182198 8.461C-0.012178 8.6905 0.0548218 8.9185 0.191822 9.104L0.242322 9.1665C0.575322 9.5485 1.15132 9.6165 1.56582 9.31L3.99982 7.5115L6.43382 9.31C6.58413 9.42115 6.76305 9.48708 6.94954 9.50006C7.13603 9.51303 7.32235 9.4725 7.4866 9.38323C7.65085 9.29397 7.78621 9.15967 7.87677 8.99613C7.96733 8.83258 8.00932 8.64659 7.99782 8.46L7.61232 2.0765C7.58622 1.64981 7.39835 1.24914 7.08701 0.956192C6.77567 0.663248 6.36431 0.500094 5.93682 0.5Z"
fill="currentColor"
/>
</svg>
);
}

View File

@@ -1,4 +1,6 @@
---
import AstroIcon from '../AstroIcon.astro';
import { MarkFavorite } from './MarkFavorite';
export interface FeaturedItemType {
isUpcoming?: boolean;
isNew?: boolean;
@@ -13,23 +15,29 @@ const { isUpcoming = false, isNew = false, text, url } = Astro.props;
<a
class:list={[
'group border border-slate-800 bg-slate-900 p-2.5 sm:p-3.5 block no-underline rounded-lg relative text-slate-400 font-regular text-md hover:border-slate-600 hover:text-slate-100',
'group border border-slate-800 bg-slate-900 p-2.5 sm:p-3.5 block no-underline rounded-lg relative text-slate-400 font-regular text-md hover:border-slate-600 hover:text-slate-100 overflow-hidden',
{
'opacity-50': isUpcoming,
},
]}
href={url}
>
<span class='text-slate-400'>
<span class='relative z-20 text-slate-400'>
{text}
</span>
<MarkFavorite
resourceId={url.split('/').pop()!}
resourceType={url.includes('best-practices') ? 'best-practice' : 'roadmap'}
client:load
/>
{
isNew && (
<span class='absolute bottom-1.5 right-2 text-xs font-medium rounded-br rounded-tl text-purple-300 flex items-center'>
<span class='flex h-2 w-2 mr-1.5'>
<span class='animate-ping absolute inline-flex h-2 w-2 rounded-full bg-purple-400 opacity-75' />
<span class='relative inline-flex rounded-full h-2 w-2 bg-purple-500' />
<span class='absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-purple-300'>
<span class='mr-1.5 flex h-2 w-2'>
<span class='absolute inline-flex h-2 w-2 animate-ping rounded-full bg-purple-400 opacity-75' />
<span class='relative inline-flex h-2 w-2 rounded-full bg-purple-500' />
</span>
New
</span>
@@ -38,13 +46,17 @@ const { isUpcoming = false, isNew = false, text, url } = Astro.props;
{
isUpcoming && (
<span class='absolute bottom-1.5 right-2 text-xs font-medium rounded-br rounded-tl text-slate-500 flex items-center'>
<span class='flex h-2 w-2 mr-1.5'>
<span class='animate-ping absolute inline-flex h-2 w-2 rounded-full bg-slate-500 opacity-75' />
<span class='relative inline-flex rounded-full h-2 w-2 bg-slate-600' />
<span class='absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-slate-500'>
<span class='mr-1.5 flex h-2 w-2'>
<span class='absolute inline-flex h-2 w-2 animate-ping rounded-full bg-slate-500 opacity-75' />
<span class='relative inline-flex h-2 w-2 rounded-full bg-slate-600' />
</span>
Upcoming
</span>
)
}
<span
data-progress
class='absolute bottom-0 left-0 top-0 z-10 w-0 bg-[#172a3a] transition-[width] duration-300'
></span>
</a>

View File

@@ -0,0 +1,93 @@
import { useEffect, useState } from 'preact/hooks';
import { httpPatch } from '../../lib/http';
import type { ResourceType } from '../../lib/resource-progress';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
import { FavoriteIcon } from './FavoriteIcon';
import { Spinner } from '../ReactIcons/Spinner';
type MarkFavoriteType = {
resourceType: ResourceType;
resourceId: string;
favorite?: boolean;
};
export function MarkFavorite({ resourceId, resourceType, favorite }: MarkFavoriteType) {
const [isLoading, setIsLoading] = useState(false);
const [isFavorite, setIsFavorite] = useState(favorite ?? false);
async function toggleFavoriteHandler(e: Event) {
e.preventDefault();
if (!isLoggedIn()) {
showLoginPopup();
return;
}
if (isLoading) {
return;
}
setIsLoading(true);
const { error } = await httpPatch<{ status: 'ok' }>(
`${import.meta.env.PUBLIC_API_URL}/v1-mark-favorite`,
{
resourceType,
resourceId,
}
);
if (error) {
setIsLoading(false);
return alert('Failed to update favorite status');
}
// Dispatching an event instead of setting the state because
// MarkFavorite component is used in the HeroSection as well
// as featured items section. We will let the custom event
// listener set the update `useEffect`
window.dispatchEvent(
new CustomEvent('mark-favorite', {
detail: {
resourceId,
resourceType,
isFavorite: !isFavorite,
},
})
);
window.dispatchEvent(new CustomEvent('refresh-favorites', {}));
setIsFavorite(!isFavorite);
setIsLoading(false);
}
useEffect(() => {
const listener = (e: Event) => {
const {
resourceId: id,
resourceType: type,
isFavorite: fav,
} = (e as CustomEvent).detail;
if (id === resourceId && type === resourceType) {
setIsFavorite(fav);
}
};
window.addEventListener('mark-favorite', listener);
return () => {
window.removeEventListener('mark-favorite', listener);
};
}, []);
return (
<button
onClick={toggleFavoriteHandler}
tabIndex={-1}
className={`${
isFavorite ? '' : 'opacity-30 hover:opacity-100'
} absolute right-1.5 top-1.5 z-30 focus:outline-0`}
>
{isLoading ? <Spinner /> : <FavoriteIcon isFavorite={isFavorite} />}
</button>
);
}

View File

@@ -2,13 +2,19 @@ import { wireframeJSONToSVG } from 'roadmap-renderer';
import { httpPost } from '../../lib/http';
import { isLoggedIn } from '../../lib/jwt';
import {
refreshProgressCounters,
renderResourceProgress,
renderTopicProgress,
ResourceProgressType,
ResourceType,
updateResourceProgress,
} from '../../lib/resource-progress';
import { pageProgressMessage } from '../../stores/page';
import { showLoginPopup } from '../../lib/popup';
export class Renderer {
resourceId: string;
resourceType: string;
resourceType: ResourceType | string;
jsonUrl: string;
loaderHTML: string | null;
@@ -28,8 +34,10 @@ export class Renderer {
this.onDOMLoaded = this.onDOMLoaded.bind(this);
this.jsonToSvg = this.jsonToSvg.bind(this);
this.handleSvgClick = this.handleSvgClick.bind(this);
this.handleSvgRightClick = this.handleSvgRightClick.bind(this);
this.prepareConfig = this.prepareConfig.bind(this);
this.switchRoadmap = this.switchRoadmap.bind(this);
this.updateTopicStatus = this.updateTopicStatus.bind(this);
}
get loaderEl() {
@@ -161,6 +169,53 @@ export class Renderer {
this.jsonToSvg(newJsonUrl)?.then(() => {});
}
updateTopicStatus(topicId: string, newStatus: ResourceProgressType) {
if (!isLoggedIn()) {
showLoginPopup();
return;
}
pageProgressMessage.set('Updating progress');
updateResourceProgress(
{
resourceId: this.resourceId,
resourceType: this.resourceType as ResourceType,
topicId,
},
newStatus
)
.then(() => {
renderTopicProgress(topicId, newStatus);
refreshProgressCounters();
})
.catch((err) => {
alert('Something went wrong, please try again.');
console.error(err);
})
.finally(() => {
pageProgressMessage.set('');
});
return;
}
handleSvgRightClick(e: any) {
const targetGroup = e.target?.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
if (!groupId) {
return;
}
e.preventDefault();
const isCurrentStatusDone = targetGroup.classList.contains('done');
const normalizedGroupId = groupId.replace(/^\d+-/, '');
this.updateTopicStatus(
normalizedGroupId,
!isCurrentStatusDone ? 'done' : 'pending'
);
}
handleSvgClick(e: any) {
const targetGroup = e.target?.closest('g') || {};
const groupId = targetGroup.dataset ? targetGroup.dataset.groupId : '';
@@ -209,6 +264,28 @@ export class Renderer {
// Remove sorting prefix from groupId
const normalizedGroupId = groupId.replace(/^\d+-/, '');
const isCurrentStatusLearning = targetGroup.classList.contains('learning');
const isCurrentStatusSkipped = targetGroup.classList.contains('skipped');
if (e.shiftKey) {
e.preventDefault();
this.updateTopicStatus(
normalizedGroupId,
!isCurrentStatusLearning ? 'learning' : 'pending'
);
return;
}
if (e.altKey) {
e.preventDefault();
this.updateTopicStatus(
normalizedGroupId,
!isCurrentStatusSkipped ? 'skipped' : 'pending'
);
return;
}
window.dispatchEvent(
new CustomEvent(`${this.resourceType}.topic.click`, {
detail: {
@@ -223,7 +300,7 @@ export class Renderer {
init() {
window.addEventListener('DOMContentLoaded', this.onDOMLoaded);
window.addEventListener('click', this.handleSvgClick);
// window.addEventListener('contextmenu', this.handleSvgClick);
window.addEventListener('contextmenu', this.handleSvgRightClick);
}
}

View File

@@ -0,0 +1,23 @@
import { CheckIcon } from '../ReactIcons/CheckIcon';
type EmptyProgressProps = {
title?: string;
message?: string;
};
export function EmptyProgress(props: EmptyProgressProps) {
const {
title = 'Start learning ..',
message = 'Your progress and favorite roadmaps will show up here.',
} = props;
return (
<div className="relative flex min-h-full flex-col items-start sm:items-center justify-center py-6">
<h2 className={'mb-1 flex items-center text-lg sm:text-2xl text-gray-200'}>
<CheckIcon additionalClasses='mr-2 top-[0.5px] w-[16px] h-[16px] sm:w-[20px] sm:h-[20px]' />
Start learning ..
</h2>
<p className={'text-gray-400 text-sm sm:text-base'}>{message}</p>
</div>
);
}

View File

@@ -0,0 +1,124 @@
import { useEffect, useState } from 'preact/hooks';
import { EmptyProgress } from './EmptyProgress';
import { httpGet } from '../../lib/http';
import { ProgressList } from './ProgressList';
export type UserProgressResponse = {
resourceId: string;
resourceType: 'roadmap' | 'best-practice';
resourceTitle: string;
isFavorite: boolean;
done: number;
learning: number;
skipped: number;
total: number;
updatedAt: Date;
}[];
function renderProgress(progressList: UserProgressResponse) {
progressList.forEach((progress) => {
const href =
progress.resourceType === 'best-practice'
? `/best-practices/${progress.resourceId}`
: `/${progress.resourceId}`;
const element = document.querySelector(`a[href="${href}"]`);
if (!element) {
return;
}
window.dispatchEvent(
new CustomEvent('mark-favorite', {
detail: {
resourceId: progress.resourceId,
resourceType: progress.resourceType,
isFavorite: progress.isFavorite,
},
})
);
const totalDone = progress.done + progress.skipped;
const percentageDone = (totalDone / progress.total) * 100;
const progressBar: HTMLElement | null =
element.querySelector('[data-progress]');
if (progressBar) {
progressBar.style.width = `${percentageDone}%`;
}
});
}
export function FavoriteRoadmaps() {
const [isPreparing, setIsPreparing] = useState(true);
const [isLoading, setIsLoading] = useState(true);
const [progress, setProgress] = useState<UserProgressResponse>([]);
const [containerOpacity, setContainerOpacity] = useState(0);
function showProgressContainer() {
const heroEl = document.getElementById('hero-text')!;
if (!heroEl) {
return;
}
heroEl.classList.add('opacity-0');
setTimeout(() => {
heroEl.parentElement?.removeChild(heroEl);
setIsPreparing(false);
setTimeout(() => {
setContainerOpacity(100);
}, 50);
}, 0);
}
async function loadProgress() {
setIsLoading(true);
const { response: progressList, error } =
await httpGet<UserProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
);
if (error || !progressList) {
return;
}
setProgress(progressList);
setIsLoading(false);
showProgressContainer();
// render progress on featured items
renderProgress(progressList);
}
useEffect(() => {
loadProgress().finally(() => {
setIsLoading(false);
});
}, []);
useEffect(() => {
window.addEventListener('refresh-favorites', loadProgress);
return () => window.removeEventListener('refresh-favorites', loadProgress);
}, []);
if (isPreparing) {
return null;
}
const hasProgress = progress.length > 0;
return (
<div
class={`flex min-h-[192px] bg-gradient-to-b transition-opacity duration-500 sm:min-h-[280px] opacity-${containerOpacity} ${
hasProgress && `border-t border-t-[#1e293c]`
}`}
>
<div className="container min-h-full">
{!isLoading && progress.length == 0 && <EmptyProgress />}
{progress.length > 0 && (
<ProgressList progress={progress} isLoading={isLoading} />
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,28 @@
---
import { FavoriteRoadmaps } from './FavoriteRoadmaps';
---
<div class='relative min-h-auto min-h-[192px] sm:min-h-[281px] border-b border-b-[#1e293c]'>
<div
class='container px-6 py-6 pb-14 text-left sm:px-0 sm:py-20 sm:text-center transition-opacity duration-300'
id='hero-text'
>
<h1
class='mb-2 bg-gradient-to-b from-amber-50 to-purple-500 bg-clip-text text-2xl font-bold text-transparent sm:mb-4 sm:text-5xl'
>
Developer Roadmaps
</h1>
<p class='hidden px-4 text-lg text-gray-400 sm:block'>
<span class='font-medium text-gray-400'>roadmap.sh</span> is a community effort
to create roadmaps, guides and other educational content to help guide developers
in picking up a path and guide their learnings.
</p>
<p class='text-md block px-0 text-gray-400 sm:hidden'>
Community created roadmaps, guides and articles to help developers grow in
their career.
</p>
</div>
<FavoriteRoadmaps client:authenticated />
</div>

View File

@@ -0,0 +1,61 @@
import type { UserProgressResponse } from './FavoriteRoadmaps';
import { CheckIcon } from '../ReactIcons/CheckIcon';
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
import { Spinner } from '../ReactIcons/Spinner';
type ProgressListProps = {
progress: UserProgressResponse;
isLoading?: boolean;
};
export function ProgressList(props: ProgressListProps) {
const { progress, isLoading = false } = props;
return (
<div className="relative pb-12 pt-4 sm:pt-7">
<p className="mb-4 flex items-center text-sm text-gray-400">
{!isLoading && (
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
)}
{isLoading && (
<span className="mr-1.5">
<Spinner />
</span>
)}
Your progress and favorite roadmaps.
</p>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
{progress.map((resource) => {
const url =
resource.resourceType === 'roadmap'
? `/${resource.resourceId}`
: `/best-practices/${resource.resourceId}`;
const percentageDone =
((resource.skipped + resource.done) / resource.total) * 100;
return (
<a
key={resource.resourceId}
href={url}
className="relative flex flex-col overflow-hidden rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300"
>
<span className="relative z-20">{resource.resourceTitle}</span>
<span
class="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]"
style={{ width: `${percentageDone}%` }}
></span>
<MarkFavorite
resourceId={resource.resourceId}
resourceType={resource.resourceType}
favorite={resource.isFavorite}
/>
</a>
);
})}
</div>
</div>
);
}

View File

@@ -5,7 +5,11 @@ import AccountDropdown from './AccountDropdown.astro';
<div class='bg-slate-900 py-5 text-white sm:py-8'>
<nav class='container flex items-center justify-between'>
<a class='flex items-center text-lg font-medium text-white' href='/' aria-label="roadmap.sh">
<a
class='flex items-center text-lg font-medium text-white'
href='/'
aria-label='roadmap.sh'
>
<Icon icon='logo' />
</a>
@@ -26,9 +30,12 @@ import AccountDropdown from './AccountDropdown.astro';
<a href='/videos' class='text-gray-400 hover:text-white'>Videos</a>
</li>
<li>
<kbd data-command-menu class="hidden sm:flex items-center text-gray-400 border border-gray-800 rounded-md px-2.5 py-1 text-sm hover:bg-gray-800 hover:cursor-pointer">
<Icon icon='search' class='h-3 w-3 mr-2' />
<kbd class='font-sans mr-1'>⌘</kbd><kbd class='font-sans'>K</kbd>
<kbd
data-command-menu
class='hidden items-center rounded-md border border-gray-800 px-2.5 py-1 text-sm text-gray-400 hover:cursor-pointer hover:bg-gray-800 sm:flex'
>
<Icon icon='search' class='mr-2 h-3 w-3' />
<kbd class='mr-1 font-sans'>⌘</kbd><kbd class='font-sans'>K</kbd>
</kbd>
</li>
</ul>
@@ -97,10 +104,7 @@ import AccountDropdown from './AccountDropdown.astro';
<!-- Links for logged in users -->
<li data-auth-required class='hidden'>
<a
href='/account'
class='text-xl hover:text-blue-300 md:text-lg'
>
<a href='/account' class='text-xl hover:text-blue-300 md:text-lg'>
Account
</a>
</li>

View File

@@ -29,6 +29,18 @@ export function PageSponsor(props: PageSponsorProps) {
const [sponsor, setSponsor] = useState<PageSponsorType>();
const loadSponsor = async () => {
const currentPath = window.location.pathname;
if (
currentPath === '/' ||
currentPath === '/best-practices' ||
currentPath === '/roadmaps' ||
currentPath.startsWith('/guides') ||
currentPath.startsWith('/videos') ||
currentPath.startsWith('/account')
) {
return;
}
const { response, error } = await httpGet<V1GetSponsorResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-sponsor`,
{

View File

@@ -19,6 +19,7 @@ export class Popup {
return;
}
e.preventDefault();
popupEl.classList.remove('hidden');
popupEl.classList.add('flex');
const focusEl = popupEl.querySelector('[autofocus]');

View File

@@ -0,0 +1,47 @@
---
import AstroIcon from './AstroIcon.astro';
import Popup from './Popup/Popup.astro';
---
<Popup id='progress-help' title='' subtitle=''>
<div class='-mt-2.5'>
<h2 class='mb-3 text-2xl font-semibold leading-5 text-gray-900'>
Track your Progress
</h2>
<p class='text-sm leading-4 text-gray-600'>
Login and use one of the options listed below.
</p>
<div class='mt-4 flex flex-col gap-1.5'>
<div class='rounded-md border px-3 py-3 text-gray-500'>
<span class='mb-1.5 block text-xs font-medium uppercase text-green-600'
>Option 1</span
>
<p class='text-sm'>
Click the roadmap topics and use <span class='underline'
>Update Progress</span
> dropdown to update your progress.
</p>
</div>
<div class='rounded-md border border-yellow-300 bg-yellow-50 px-3 py-3 text-gray-500'>
<span class='mb-1.5 block text-xs font-medium uppercase text-green-600'
>Option 2</span
>
<p class='text-sm'>Use the keyboard shortcuts listed below.</p>
<ul class="flex flex-col gap-1 mt-3 mb-1.5">
<li class='text-sm leading-loose'>
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Right Mouse Click</kbd> to mark as Done.
</li>
<li class='text-sm leading-loose'>
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Shift</kbd> + <kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Click</kbd> to mark as in progress.
</li>
<li class='text-sm leading-loose'>
<kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Option / Alt</kbd> + <kbd class="px-2 py-1.5 text-xs text-white bg-gray-900 rounded-md">Click</kbd> to mark as skipped.
</li>
</ul>
</div>
</div>
</div>
</Popup>

View File

@@ -0,0 +1,20 @@
type CheckIconProps = {
additionalClasses?: string;
};
export function CheckIcon(props: CheckIconProps) {
const { additionalClasses = 'mr-2 top-[0.5px] w-[20px] h-[20px]' } = props;
return (
<svg
className={`relative ${additionalClasses}]`}
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"></path>
</svg>
);
}

View File

@@ -0,0 +1,21 @@
export function Spinner() {
return (
<svg
className="h-3.5 w-3.5 animate-spin"
viewBox="0 0 93 93"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M46.5 93C72.1812 93 93 72.1812 93 46.5C93 20.8188 72.1812 0 46.5 0C20.8188 0 0 20.8188 0 46.5C0 72.1812 20.8188 93 46.5 93ZM46.5 77C63.3447 77 77 63.3447 77 46.5C77 29.6553 63.3447 16 46.5 16C29.6553 16 16 29.6553 16 46.5C16 63.3447 29.6553 77 46.5 77Z"
style="fill: #404040;"
></path>
<path
d="M84.9746 49.5667C89.3257 49.9135 93.2042 46.6479 92.81 42.3008C92.3588 37.3251 91.1071 32.437 89.0872 27.8298C86.0053 20.7998 81.2311 14.6422 75.1905 9.90623C69.15 5.17027 62.031 2.00329 54.4687 0.687889C49.5126 -0.174203 44.467 -0.223422 39.5274 0.525737C35.2118 1.18024 32.966 5.72596 34.3411 9.86865V9.86865C35.7161 14.0113 40.2118 16.1424 44.5681 15.8677C46.9635 15.7166 49.3773 15.8465 51.7599 16.2609C56.7515 17.1291 61.4505 19.2196 65.4377 22.3456C69.4249 25.4717 72.5762 29.5362 74.6105 34.1764C75.5815 36.3912 76.2835 38.7044 76.7084 41.0666C77.4811 45.3626 80.6234 49.2199 84.9746 49.5667V49.5667Z"
style="fill: #94a3b8;"
></path>
</svg>
);
}

View File

@@ -0,0 +1,68 @@
---
import AstroIcon from './AstroIcon.astro';
export interface Props {
isSecondaryBanner?: boolean;
}
const { isSecondaryBanner = false } = Astro.props;
---
<div
data-progress-nums-container
class:list={[
'hidden sm:flex justify-between px-2 bg-white items-center py-1.5 relative striped-loader bg-white',
{
'rounded-bl-md rounded-br-md': isSecondaryBanner,
'rounded-md': !isSecondaryBanner,
},
]}
>
<p
class='flex text-sm opacity-0 transition-opacity duration-300'
data-progress-nums
>
<span
class='mr-2.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
>
<span data-progress-percentage>0</span>% Done
</span>
<span><span data-progress-done>0</span> completed</span><span
class='mx-1.5 text-gray-400'>&middot;</span
>
<span><span data-progress-learning>0</span> in progress</span><span
class='mx-1.5 text-gray-400'>&middot;</span
>
<span><span data-progress-skipped>0</span> skipped</span><span
class='mx-1.5 text-gray-400'>&middot;</span
>
<span><span data-progress-total>0</span> Total</span>
</p>
<button
data-popup='progress-help'
class='flex items-center gap-1 text-sm font-medium text-gray-500 opacity-0 transition-opacity hover:text-black'
data-progress-nums
>
<AstroIcon icon='question' />
Track Progress
</button>
</div>
<p
data-progress-nums-container
class='striped-loader relative -mb-2 flex items-center justify-between rounded-md border bg-white bg-white px-2 py-1.5 text-sm text-sm text-gray-700 sm:hidden'
>
<span data-progress-nums class='opacity-0 transition-opacity duration-300 text-gray-500'>
<span data-progress-done>0</span> of <span data-progress-total>0</span> Done
</span>
<button
data-popup='progress-help'
class='flex items-center gap-1 text-sm font-medium text-gray-500 opacity-0 transition-opacity hover:text-black'
data-progress-nums
>
<AstroIcon icon='question' />
Track Progress
</button>
</p>

View File

@@ -0,0 +1,42 @@
import { useCopyText } from '../../hooks/use-copy-text';
import CopyIcon from '../../icons/copy.svg';
type EditorProps = {
title: string;
text: string;
};
export function Editor(props: EditorProps) {
const { text, title } = props;
const { isCopied, copyText } = useCopyText();
return (
<div className="flex w-full flex-grow flex-col overflow-hidden rounded border border-gray-300 bg-gray-50">
<div className="flex items-center justify-between gap-2 border-b border-gray-300 px-3 py-2">
<span className="text-xs uppercase leading-none text-gray-400">
{title}
</span>
<button className="flex items-center" onClick={() => copyText(text)}>
{isCopied && (
<span className="mr-1 text-xs leading-none text-gray-700">
Copied!
</span>
)}
<img src={CopyIcon} alt="Copy" className="inline-block h-4 w-4" />
</button>
</div>
<textarea
className="no-scrollbar block h-12 w-full overflow-x-auto whitespace-nowrap bg-gray-200/70 p-3 text-sm text-gray-900 focus:bg-gray-50 focus:outline-0"
readOnly
onClick={(e: any) => {
e.target.select();
copyText(e.target.value);
}}
>
{text}
</textarea>
</div>
);
}

View File

@@ -0,0 +1,15 @@
export function GitHubReadmeBanner() {
return (
<p className="mt-3 rounded-md border p-2 text-sm w-full bg-yellow-100 border-yellow-400 text-yellow-900">
Add this badge to your{' '}
<a
href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline hover:text-blue-800"
>
GitHub profile readme.
</a>
</p>
);
}

View File

@@ -0,0 +1,172 @@
import { useState } from 'preact/hooks';
import { useCopyText } from '../../hooks/use-copy-text';
import { useAuth } from '../../hooks/use-auth';
import CopyIcon from '../../icons/copy.svg';
import { RoadmapSelect } from './RoadmapSelect';
import { GitHubReadmeBanner } from './GitHubReadmeBanner';
import { downloadImage } from '../../helper/download-image';
import { SelectionButton } from './SelectionButton';
import { StepCounter } from './StepCounter';
import { Editor } from './Editor';
type StepLabelProps = {
label: string;
};
function StepLabel(props: StepLabelProps) {
const { label } = props;
return (
<span className="mb-3 flex items-center gap-2 text-sm leading-none text-gray-400">
{label}
</span>
);
}
export function RoadCardPage() {
const { isCopied, copyText } = useCopyText();
const [roadmaps, setRoadmaps] = useState<string[]>([]);
const [version, setVersion] = useState<'tall' | 'wide'>('tall');
const [variant, setVariant] = useState<'dark' | 'light'>('dark');
const user = useAuth();
if (!user) {
return null;
}
const badgeUrl = new URL(
`${import.meta.env.PUBLIC_API_URL}/v1-badge/${version}/${user?.id}`
);
badgeUrl.searchParams.set('variant', variant);
if (roadmaps.length > 0) {
badgeUrl.searchParams.set('roadmaps', roadmaps.join(','));
}
return (
<>
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b pt-2 pb-4">
<StepCounter step={1} />
<div>
<StepLabel label="Pick progress to show (Max. 4)" />
<div className="flex flex-wrap">
<RoadmapSelect
selectedRoadmaps={roadmaps}
setSelectedRoadmaps={setRoadmaps}
/>
</div>
</div>
</div>
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
<StepCounter step={2} />
<div>
<StepLabel label="Select Mode (Dark vs Light)" />
<div className="flex gap-2">
<SelectionButton
text={'Dark'}
isDisabled={false}
isSelected={variant === 'dark'}
onClick={() => {
setVariant('dark');
}}
/>
<SelectionButton
text={'Light'}
isDisabled={false}
isSelected={variant === 'light'}
onClick={() => {
setVariant('light');
}}
/>
</div>
</div>
</div>
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
<StepCounter step={3} />
<div>
<StepLabel label="Select Version" />
<div className="flex gap-2">
<SelectionButton
text={'Tall'}
isDisabled={false}
isSelected={version === 'tall'}
onClick={() => {
setVersion('tall');
}}
/>
<SelectionButton
text={'Wide'}
isDisabled={false}
isSelected={version === 'wide'}
onClick={() => {
setVersion('wide');
}}
/>
</div>
</div>
</div>
<div className="flex items-start gap-4 mx-0 sm:-mx-10 px-0 sm:px-10 border-b py-4">
<StepCounter step={4} />
<div class="flex-grow">
<StepLabel label="Share your #RoadCard with others" />
<div className={'rounded-md border bg-gray-50 p-2 text-center'}>
<a
href={badgeUrl.toString()}
target="_blank"
rel="noopener noreferrer"
className={`relative block hover:cursor-pointer ${
version === 'tall' ? ' max-w-[270px] ' : ' w-full '
}`}
>
<img src={badgeUrl.toString()} alt="RoadCard" />
</a>
</div>
<div className="mt-3 grid grid-cols-2 gap-2">
<button
className="flex items-center justify-center rounded border border-gray-300 p-1.5 px-2 text-sm font-medium"
onClick={() =>
downloadImage({
url: badgeUrl.toString(),
name: 'road-card',
scale: 4,
})
}
>
Download
</button>
<button
disabled={isCopied}
className="flex cursor-pointer items-center justify-center rounded border border-gray-300 p-1.5 px-2 text-sm font-medium disabled:bg-blue-50"
onClick={() => copyText(badgeUrl.toString())}
>
<img alt="Copy" src={CopyIcon} className="mr-1" />
{isCopied ? 'Copied!' : 'Copy Link'}
</button>
</div>
<div className="mt-3 flex flex-col gap-3">
<Editor
title={'HTML'}
text={`<a href="https://roadmap.sh"><img src="${badgeUrl}" alt="roadmap.sh"/></a>`.trim()}
/>
<Editor
title={'Markdown'}
text={`[![roadmap.sh](${badgeUrl})](https://roadmap.sh)`.trim()}
/>
</div>
<GitHubReadmeBanner />
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,68 @@
import { httpGet } from '../../lib/http';
import { useEffect, useState } from 'preact/hooks';
import { pageProgressMessage } from '../../stores/page';
import type { UserProgressResponse } from '../HeroSection/FavoriteRoadmaps';
import { SelectionButton } from './SelectionButton';
type RoadmapSelectProps = {
selectedRoadmaps: string[];
setSelectedRoadmaps: (updatedRoadmaps: string[]) => void;
};
export function RoadmapSelect(props: RoadmapSelectProps) {
const { selectedRoadmaps, setSelectedRoadmaps } = props;
const [progressList, setProgressList] = useState<UserProgressResponse>();
const fetchProgress = async () => {
const { response, error } = await httpGet<UserProgressResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
);
if (error || !response) {
return;
}
setProgressList(response);
};
useEffect(() => {
fetchProgress().finally(() => {
pageProgressMessage.set('');
});
}, []);
const canSelectMore = selectedRoadmaps.length < 4;
const allProgress = progressList?.filter(
(progress) => progress.resourceType === 'roadmap'
) || [];
return (
<div className="flex flex-wrap gap-1">
{allProgress?.length === 0 && <p className={'text-sm text-gray-400 italic'}>No progress tracked so far.</p>}
{allProgress?.map((progress) => {
const isSelected = selectedRoadmaps.includes(progress.resourceId);
const canSelect = isSelected || canSelectMore;
return (
<SelectionButton
text={progress.resourceTitle}
isDisabled={!canSelect}
isSelected={isSelected}
onClick={() => {
if (isSelected) {
setSelectedRoadmaps(
selectedRoadmaps.filter(
(roadmap) => roadmap !== progress.resourceId
)
);
} else if (selectedRoadmaps.length < 4) {
setSelectedRoadmaps([...selectedRoadmaps, progress.resourceId]);
}
}}
/>
);
})}
</div>
);
}

View File

@@ -0,0 +1,23 @@
type SelectionButtonProps = {
text: string;
isDisabled: boolean;
isSelected: boolean;
onClick: () => void;
};
export function SelectionButton(props: SelectionButtonProps) {
const { text, isDisabled, isSelected, onClick } = props;
return (
<button
className={`rounded-md border p-1 px-2 text-sm ${
isSelected ? ' border-gray-500 bg-gray-300 ' : ''
} ${
!isDisabled ? ' cursor-pointer ' : ' cursor-not-allowed opacity-40 '
}`}
onClick={onClick}
>
{text}
</button>
);
}

View File

@@ -0,0 +1,17 @@
type StepCounterProps = {
step: number;
};
export function StepCounter(props: StepCounterProps) {
const { step } = props;
return (
<span
className={
'flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-gray-300 text-white'
}
>
{step}
</span>
);
}

View File

@@ -2,9 +2,11 @@
import Icon from './AstroIcon.astro';
import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
import RoadmapHint from './RoadmapHint.astro';
import { MarkFavorite } from './FeaturedItems/MarkFavorite.tsx';
import RoadmapNote from './RoadmapNote.astro';
import TopicSearch from './TopicSearch/TopicSearch.astro';
import YouTubeAlert from './YouTubeAlert.astro';
import ProgressHelpPopup from './ProgressHelpPopup.astro';
export interface Props {
title: string;
@@ -32,6 +34,7 @@ const isRoadmapReady = !isUpcoming;
---
<LoginPopup />
<ProgressHelpPopup />
<div class='border-b'>
<div class='container relative py-5 sm:py-12'>

View File

@@ -1,5 +1,8 @@
---
import { ClearProgress } from './Activity/ClearProgress';
import AstroIcon from './AstroIcon.astro';
import Icon from './AstroIcon.astro';
import ResourceProgressStats from './ResourceProgressStats.astro';
export interface Props {
roadmapId: string;
@@ -41,37 +44,5 @@ const roadmapTitle =
)
}
<!-- Desktop: Roadmap Resources - Alert -->
<div
class:list={[
'hidden sm:flex justify-between px-2 bg-white items-center',
{
'rounded-bl-md rounded-br-md': hasTNSBanner,
'rounded-md': !hasTNSBanner,
},
]}
>
<p class='text-sm'>
<span
class='mr-0.5 rounded-sm bg-yellow-200 px-1 py-0.5 text-xs font-medium uppercase text-yellow-900'
>New</span
>
Track your progress and learn by clicking roadmap items.
</p>
<a
href={`/${roadmapId}/topics`}
class='inline-flex items-center justify-center rounded-md px-1 py-1.5 text-sm font-medium text-gray-500 hover:text-black'
>
<Icon icon='search' />
<span class='ml-2'>Search Topics</span>
</a>
</div>
<!-- Mobile - Roadmap resources alert -->
<p
class='relative block rounded-md border border-yellow-500 bg-white px-2 py-1.5 text-sm text-yellow-700 sm:hidden'
>
Track your progress and learn about the topics by clicking the roadmap items.
</p>
</div>
<ResourceProgressStats isSecondaryBanner={hasTNSBanner} />
</div>

View File

@@ -10,6 +10,7 @@ import { httpGet } from '../../lib/http';
import { isLoggedIn } from '../../lib/jwt';
import {
isTopicDone,
refreshProgressCounters,
renderTopicProgress,
ResourceType,
updateResourceProgress as updateResourceProgressApi,
@@ -17,6 +18,7 @@ import {
import { pageProgressMessage, sponsorHidden } from '../../stores/page';
import { TopicProgressButton } from './TopicProgressButton';
import { ContributionForm } from './ContributionForm';
import { showLoginPopup } from '../../lib/popup';
export function TopicDetail() {
const [contributionAlertMessage, setContributionAlertMessage] = useState('');
@@ -34,20 +36,6 @@ export function TopicDetail() {
const [resourceId, setResourceId] = useState('');
const [resourceType, setResourceType] = useState<ResourceType>('roadmap');
const showLoginPopup = () => {
const popupEl = document.querySelector(`#login-popup`);
if (!popupEl) {
return;
}
popupEl.classList.remove('hidden');
popupEl.classList.add('flex');
const focusEl = popupEl.querySelector<HTMLElement>('[autofocus]');
if (focusEl) {
focusEl.focus();
}
};
// Close the topic detail when user clicks outside the topic detail
useOutsideClick(topicRef, () => {
setIsActive(false);
@@ -87,6 +75,7 @@ export function TopicDetail() {
topicId,
done.includes(topicId) ? 'done' : 'pending'
);
refreshProgressCounters();
})
.catch((err) => {
alert(err.message);
@@ -186,7 +175,6 @@ export function TopicDetail() {
topicId={topicId}
resourceId={resourceId}
resourceType={resourceType}
onShowLoginPopup={showLoginPopup}
onClose={() => {
setIsActive(false);
setIsContributing(false);

View File

@@ -8,16 +8,17 @@ import {
ResourceProgressType,
ResourceType,
getTopicStatus,
refreshProgressCounters,
renderTopicProgress,
updateResourceProgress,
} from '../../lib/resource-progress';
import { showLoginPopup } from '../../lib/popup';
type TopicProgressButtonProps = {
topicId: string;
resourceId: string;
resourceType: ResourceType;
onShowLoginPopup: () => void;
onClose: () => void;
};
@@ -29,7 +30,7 @@ const statusColors: Record<ResourceProgressType, string> = {
};
export function TopicProgressButton(props: TopicProgressButtonProps) {
const { topicId, resourceId, resourceType, onClose, onShowLoginPopup } =
const { topicId, resourceId, resourceType, onClose } =
props;
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true);
@@ -118,7 +119,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
if (isGuest) {
onClose();
onShowLoginPopup();
showLoginPopup();
return;
}
@@ -135,6 +136,7 @@ export function TopicProgressButton(props: TopicProgressButtonProps) {
setProgress(progress);
onClose();
renderTopicProgress(topicId, progress);
refreshProgressCounters();
})
.catch((err) => {
alert(err.message);

View File

@@ -80,7 +80,7 @@ export default function UpdatePasswordForm() {
<form onSubmit={handleSubmit}>
<div class="hidden md:block mb-8">
<h2 className="text-3xl font-bold sm:text-4xl">Password</h2>
<p className="mt-2">Use the form below to update your password.</p>
<p className="mt-2 text-gray-400">Use the form below to update your password.</p>
</div>
<div className="space-y-4">
{authProvider === 'email' && (

View File

@@ -83,7 +83,7 @@ export function UpdateProfileForm() {
<div>
<div className="mb-8 hidden md:block">
<h2 className="text-3xl font-bold sm:text-4xl">Profile</h2>
<p className="mt-2">Update your profile details below.</p>
<p className="mt-2 text-gray-400">Update your profile details below.</p>
</div>
<UploadProfilePicture
avatarUrl={

View File

@@ -7,6 +7,6 @@
class="bg-red-600 group-hover:bg-red-800 group-hover: px-1.5 py-0.5 rounded-sm text-white text-xs uppercase font-medium mr-2"
>New</span
>
<span class="underline mr-1">Roadmap topics to be covered on YouTube</span>
<span class="underline mr-1">We also have a YouTube channel with visual content</span>
<span>&raquo;</span>
</a>

View File

@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/api-security.pdf'
order: 2
briefTitle: 'API Security'
briefDescription: 'API Security Best Practices'
isNew: true
isNew: false
isUpcoming: false
title: 'API Security Best Practices'
description: 'Detailed list of best practices to make your APIs secure'

View File

@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/aws.pdf'
order: 3
briefTitle: 'AWS'
briefDescription: 'AWS Best Practices'
isNew: true
isNew: false
isUpcoming: false
title: 'AWS Best Practices'
description: 'Detailed list of best practices for Amazon Web Services (AWS)'

View File

@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/code-review.pdf'
order: 2
briefTitle: 'Code Reviews'
briefDescription: 'Code Review Best Practices'
isNew: true
isNew: false
isUpcoming: false
title: 'Code Review Best Practices'
description: 'Detailed list of best practices for effective code reviews and quality'

View File

@@ -4,7 +4,7 @@ pdfUrl: '/pdfs/best-practices/frontend-performance.pdf'
order: 1
briefTitle: 'Frontend Performance'
briefDescription: 'Frontend Performance Best Practices'
isNew: true
isNew: false
isUpcoming: false
title: 'Frontend Performance Best Practices'
description: 'Detailed list of best practices to improve your frontend performance'

View File

@@ -6,3 +6,4 @@ Visit the following resources to learn more:
- [Union Types - typescriptlang](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html)
- [Union Type video for Beginners](https://www.youtube.com/watch?v=uxjpm4W5pCo)
- [Union Types - typescriptlang](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)

View File

@@ -9,3 +9,4 @@ Type guards are typically used for narrowing a type and are pretty similar to fe
Visit the following resources to learn more:
- [Types Guards - Blog](https://blog.logrocket.com/how-to-use-type-guards-typescript/)
- [TypeScript Type Guards Explained](https://www.youtube.com/watch?v=feeeitmtdwg)

View File

@@ -6,3 +6,4 @@ Visit the following resources to learn more:
- [C# official website?](https://learn.microsoft.com/en-us/dotnet/csharp//)
- [The Beginners Guide to C#](https://www.w3schools.com/CS/index.php)
- [C# Tutorial](https://www.w3schools.com/cs/index.php)

View File

@@ -5,3 +5,5 @@ In the ASP.NET Core framework, filters and attributes are used to add additional
- **Filters** are classes that implement one or more of the filter interfaces provided by the framework, such as `IActionFilter`, `IResultFilter`, `IExceptionFilter`, and `IAuthorizationFilter`. Filters can be applied to controllers, action methods, or globally to the entire application. They can be used to perform tasks such as logging, caching, and handling exceptions.
- **Attributes** are classes that derive from `Attribute` class, and are used to decorate controllers, action methods, or properties with additional metadata. For example, the Authorize attribute can be used to require that a user is authenticated before accessing a specific action method, and the `ValidateAntiForgeryToken` attribute can be used to protect against cross-site request forgery (CSRF) attacks.
- [Filters](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0)

View File

@@ -10,3 +10,4 @@ For more resources, visit the following links:
- [Change Tracking in EF Core](https://learn.microsoft.com/en-us/ef/core/change-tracking/)
- [Intro to Change Tracking](https://www.oreilly.com/library/view/programming-entity-framework/9781449331825/ch05.html)
- [ChangeTracker in Entity Framework Core](https://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspxs)
- [ChangeTracker in Entity Framework Core](https://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx)

View File

@@ -11,4 +11,4 @@ Visit the following resources to learn more:
- [HTTP/3 From A To Z: Core Concepts](https://www.smashingmagazine.com/2021/08/http3-core-concepts-part1/)
- [HTTP/1 to HTTP/2 to HTTP/3](https://www.youtube.com/watch?v=a-sBfyiXysI)
- [HTTP Crash Course & Exploration](https://www.youtube.com/watch?v=iYM2zFP3Zn0)
- [SSL, TLS, HTTPS Explained](https://www.youtube.com/watch?v=j9qmmewmcfo)
- [SSL, TLS, HTTPS Explained](https://www.youtube.com/watch?v=j9QmMEWmcfo)

View File

@@ -10,3 +10,4 @@ Visit the following resources to learn more:
- [Codeacademy - Free Course](https://www.codecademy.com/learn/learn-java)
- [W3 Schools Tutorials](https://www.w3schools.com/java/)
- [Java Crash Course](https://www.youtube.com/watch?v=eIrMbAQSU34)
- [Complete Java course](https://www.youtube.com/watch?v=xk4_1vdrzzo)

View File

@@ -11,3 +11,4 @@ Visit the following resources to learn more:
- [Git vs. GitHub: Whats the difference?](https://www.youtube.com/watch?v=wpISo9TNjfU)
- [Git and GitHub for Beginners](https://www.youtube.com/watch?v=RGOj5yH7evk)
- [Git and GitHub - CS50 Beyond 2019](https://www.youtube.com/watch?v=eulnSXkhE7I)
- [Learn Git Branching](https://learngitbranching.js.org/?locale=en_us)

View File

@@ -7,4 +7,5 @@ Visit the following resources to learn more:
- [MySQL website](https://www.mysql.com/)
- [W3Schools - MySQL tutorial ](https://www.w3schools.com/mySQl/default.asp)
- [MySQL tutorial for beginners](https://www.youtube.com/watch?v=7S_tz1z_5bA)
- [MySQL for Developers](https://planetscale.com/courses/mysql-for-developers/introduction/course-introduction)
- [MySQL for Developers](https://planetscale.com/courses/mysql-for-developers/introduction/course-introduction)
- [MySQL Tutorial](https://www.mysqltutorial.org/)

View File

@@ -9,3 +9,4 @@ Visit the following resources to learn more:
- [What is OpenID](https://openid.net/connect/)
- [OAuth vs OpenID](https://securew2.com/blog/oauth-vs-openid-which-is-better)
- [An Illustrated Guide to OAuth and OpenID Connect](https://www.youtube.com/watch?v=t18YB3xDfXI)
- [OAuth 2.0 and OpenID Connect (in plain English)](https://www.youtube.com/watch?v=996oiexhze0)

View File

@@ -8,3 +8,4 @@ Visit the following resources to learn more:
- [Blockchain Explained](https://www.investopedia.com/terms/b/blockchain.asp)
- [How does a blockchain work?](https://youtu.be/SSo_EIwHSd4)
- [What Is a Blockchain? | Blockchain Basics for Developers](https://youtu.be/4ff9esY_4aU)
- [An Elementary and Slightly Distilled Introduction to Blockchain](https://markpetherbridge.co.uk/blog/an-elementary-and-slightly-distilled-introduction-to-blockchain/)

View File

@@ -7,3 +7,4 @@ Visit the following resources to learn more:
- [Blockchain Fork](<https://en.wikipedia.org/wiki/Fork_(blockchain)>)
- [What is a fork?](https://www.coinbase.com/learn/crypto-basics/what-is-a-fork)
- [What Is a Hard Fork?](https://www.investopedia.com/terms/h/hard-fork.asp)
- [Blockchain Forking](https://www.youtube.com/watch?v=bu1gcyyfz7w)

View File

@@ -6,7 +6,7 @@ briefTitle: 'Computer Science'
briefDescription: 'Curriculum with free resources for a self-taught developer.'
title: 'Computer Science'
description: 'Computer Science curriculum with free resources for a self-taught developer.'
isNew: true
isNew: false
hasTopics: true
dimensions:
width: 968

View File

@@ -6,3 +6,6 @@ Visit the following resources to learn more:
- [Learn Cpp](https://learncpp.com/)
- [C++ Reference](https://en.cppreference.com/)
- [C++ TutorialsPoint](https://www.tutorialspoint.com/cplusplus/index.htm)
- [W3Schools C++](https://www.w3schools.com/cpp/default.asp)
- [C++ Roadmap](https://roadmap.sh/cpp)

View File

@@ -8,3 +8,4 @@ Visit the following resources to learn more:
- [Learn C - Tutorials Point](https://www.tutorialspoint.com/cprogramming/index.htm)
- [C Programming Tutorial for Beginners](https://www.youtube.com/watch?v=KJgsSFOSQv0)
- [Learn C Programming with Dr. Chuck](https://www.youtube.com/watch?v=j-_s8f5K30I)
- [C Programming Full Course (Bro Code)](https://www.youtube.com/watch?v=87sh2cn0s9a)

View File

@@ -6,3 +6,4 @@ Visit the following resources to learn more:
- [Breadth First Search or BFS for a Graph](https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/)
- [Graph Algorithms II - DFS, BFS, Kruskals Algorithm, Union Find Data Structure - Lecture 7](https://www.youtube.com/watch?v=ufj5_bppBsA&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=7)
- [Breadth-first search in 4 minutes](https://www.youtube.com/watch?v=hz5ytanv5qe)

View File

@@ -45,4 +45,4 @@ int main() {
In the above program, we define a simple function `add` and a class `Calculator` with a member function `multiply`. The `main` function demonstrates how to use these to perform basic arithmetic.
- [C++ Tutorial for Beginners - Full Course](https://youtu.be/vlnpwxzdw4y)
- [C++ Tutorial for Beginners - Full Course](https://www.youtube.com/watch?v=vlnpwxzdw4y)

View File

@@ -129,4 +129,5 @@ int main() {
This basic introduction to C++ should provide you with a good foundation for further learning. Explore more topics such as classes, objects, inheritance, polymorphism, templates, and the Standard Template Library (STL) to deepen your understanding of C++ and start writing more advanced programs.
- [LearnC++](https://www.learncpp.com/)
- [LearnC++](https://www.learncpp.com/)
- [C++ Full Course by freeCodeCamp](https://www.youtube.com/watch?v=vlnpwxzdw4y)

View File

@@ -10,4 +10,6 @@ Code editors are programs specifically designed for editing, managing and writin
These are just a few examples, and there are many other code editors available, including Atom, Notepad++, and Geany. They all have their features and may suit different developers' needs. Finding the right code editor is often a matter of personal preference and workflow.
To work with C++ in your chosen code editor, you often need to install some additional tools and add-ons, such as compilers, linters, and debugger support. Make sure to follow the instructions provided by the editor's documentation to set up C++ correctly.
To work with C++ in your chosen code editor, you often need to install some additional tools and add-ons, such as compilers, linters, and debugger support. Make sure to follow the instructions provided by the editor's documentation to set up C++ correctly.
- [Using C++ on Linux in VSCode](https://code.visualstudio.com/docs/cpp/config-linux)

View File

@@ -59,4 +59,7 @@ auto updateDays = [&expiresInDays](int newDays) {
updateDays(30); // expiresInDays = 30
```
Note that, when using the capture by reference, any change made to the captured variable *inside* the lambda function will affect its value in the surrounding scope.
Note that, when using the capture by reference, any change made to the captured variable *inside* the lambda function will affect its value in the surrounding scope.
- [Lambdas in C++](https://youtu.be/mwgmbbz0y8c)
- [Lambda Expressions](https://en.cppreference.com/w/cpp/language/lambda)

View File

@@ -71,4 +71,6 @@ int multiplyNumbers(int x, int y) {
}
```
In this example, we use a function prototype for `multiplyNumbers()` before defining it. This way, we can call the function from the `main()` function even though it hasn't been defined yet in the code.
In this example, we use a function prototype for `multiplyNumbers()` before defining it. This way, we can call the function from the `main()` function even though it hasn't been defined yet in the code.
- [introduction to functions in c++](https://www.learncpp.com/cpp-tutorial/introduction-to-functions/)

View File

@@ -45,4 +45,6 @@ int main() {
}
```
In this example, we create a `shared_ptr` named `shared` that manages a `MyClass` object. By assigning it to a `weak_ptr` named `weak`, we store a non-owning reference to the object. Inside the inner scope, we create a new `shared_ptr` named `sharedFromWeak` using `weak.lock()` to safely use the object. After the inner scope, the `MyClass` object is destroyed since `shared` goes out of scope, and any further attempt to create a `shared_ptr` from `weak` will fail as the object is already destroyed.
In this example, we create a `shared_ptr` named `shared` that manages a `MyClass` object. By assigning it to a `weak_ptr` named `weak`, we store a non-owning reference to the object. Inside the inner scope, we create a new `shared_ptr` named `sharedFromWeak` using `weak.lock()` to safely use the object. After the inner scope, the `MyClass` object is destroyed since `shared` goes out of scope, and any further attempt to create a `shared_ptr` from `weak` will fail as the object is already destroyed.
- [CPP Reference](https://en.cppreference.com/w/cpp/memory/weak_ptr)

View File

@@ -39,10 +39,10 @@ The third stage is converting the compiler's intermediate representation into as
**Code Example (x86 Assembly):**
```assembly
mov eax, 10
mov ebx, 20
add eax, ebx
```
mov eax, 10
mov ebx, 20
add eax, ebx
```
## Linking
@@ -55,4 +55,4 @@ The final stage is the linking of the object code with the necessary libraries a
$ g++ main.o -o main -lm
```
In summary, the compilation process in C++ involves four primary stages: preprocessing, compilation, assembly, and linking. Each stage plays a crucial role in transforming the source code into an executable program.
In summary, the compilation process in C++ involves four primary stages: preprocessing, compilation, assembly, and linking. Each stage plays a crucial role in transforming the source code into an executable program.

View File

@@ -68,4 +68,6 @@ int main()
}
```
In the above example, Poco is used to send an HTTP GET request and process the response. It manages tasks like connecting to the server, handling exceptions, and managing HTTP headers.
In the above example, Poco is used to send an HTTP GET request and process the response. It manages tasks like connecting to the server, handling exceptions, and managing HTTP headers.
- [Official Docs for Poco Library](https://docs.pocoproject.org/)

View File

@@ -33,3 +33,6 @@ To minimize the risks associated with NFC, follow these best practices:
- Ensure you're using trusted and secure apps to handle your NFC transactions.
In conclusion, understanding the basics of NFC and adhering to security best practices will help ensure that you can safely and effectively use this innovative technology.
- [The Beginner's Guide to NFCs](https://www.spiceworks.com/tech/networking/articles/what-is-near-field-communication/)
- [NFC Guide: All You Need to Know About Near Field Communication](https://squareup.com/us/en/the-bottom-line/managing-your-finances/nfc)

View File

@@ -39,3 +39,5 @@ To protect yourself and your devices, follow these best practices:
- **Use a Virtual Private Network (VPN)**: Connect to the internet using a VPN, which provides a secure, encrypted tunnel for data transmission.
By understanding the potential security risks associated with WiFi connections and following these best practices, you can enjoy the convenience, flexibility, and mobility of WiFi while ensuring a secure browsing experience.
- [Wireless Networks - Howstuffworks](https://computer.howstuffworks.com/wireless-network.htm)

View File

@@ -41,3 +41,6 @@ Logs are records of system events, application behavior, and user activity, whic
- **Leverage log-analysis tools**: Utilize specialized tools or scripts to help parse, filter, and analyze large or complex log files.
In conclusion, developing OS-independent troubleshooting skills allows you to effectively diagnose and resolve issues on any system. By following a structured approach, understanding common symptoms, and utilizing the appropriate tools, you can minimize downtime and maintain the security and efficiency of your organization's IT systems.
- [How to identify 9 signs of Operating System.](https://bro4u.com/blog/how-to-identify-9-signs-of-operating-system)
- [Trouble shooting guide](https://cdnsm5-ss6.sharpschool.com/userfiles/servers/server_20856499/file/teacher%20pages/lindsay%20dolezal/it%20essentials/5.6.pdf)

View File

@@ -27,3 +27,5 @@ Apple takes the security of iCloud very seriously and has implemented multiple l
- **Secure Tokens**: Apple uses secure tokens for authentication, which means that your iCloud password is not stored on your devices or on Apple's servers.
Overall, iCloud is a convenient and secure way for Apple device users to store and synchronize their data across devices. This cloud-based service offers numerous features to ensure seamless access and enhanced protection for user data.
- [All about iCloud](https://www.intego.com/mac-security-blog/everything-you-can-do-with-icloud-the-complete-guide/)

View File

@@ -64,3 +64,5 @@ As the digital world is constantly evolving, so too are cyber threats. Therefore
- Regularly backing up data
By honing these basic IT skills, you will be better prepared to navigate and protect your digital life, as well as making the most of the technology at your fingertips.
- [IT skills Training for beginners | Complete Course](https://youtu.be/on6dsip5yw0)

View File

@@ -21,3 +21,6 @@ Some common DNS record types you might encounter include:
- **TXT (Text) Record**: Contains human-readable or machine-readable text, often used for verification purposes or providing additional information about a domain.
As an essential part of the internet, the security and integrity of the DNS infrastructure are crucial. However, it's vulnerable to various types of cyber attacks, such as DNS cache poisoning, Distributed Denial of Service (DDoS) attacks, and DNS hijacking. Proper DNS security measures, such as DNSSEC (DNS Security Extensions) and monitoring unusual DNS traffic patterns, can help mitigate risks associated with these attacks.
- [DNS in detail (TryHackMe)](https://tryhackme.com/room/dnsindetail)
- [DNS in 100 Seconds (YouTube)](https://www.youtube.com/watch?v=uvr9lhugayu)

View File

@@ -31,3 +31,5 @@ Proper key management is crucial to maintain the security of encrypted data. Key
Cryptanalysis is the process of attempting to break cryptographic systems, often by exploiting weaknesses in the algorithms, protocols, or key management processes. The strength of a cryptographic system lies in its resistance to cryptanalysis. As a cyber security professional, understanding cryptanalysis techniques can help you identify and protect against potential vulnerabilities in your organization's cryptographic infrastructure.
In conclusion, cryptography is a fundamental aspect of cyber security, offering a layer of protection for sensitive data in digital networks. To effectively implement cryptography in your organization, you should be familiar with the various types of cryptography, cryptographic protocols, and key management best practices, and understand the potential threats posed by cryptanalysis.
- [Cryptography for Dummies (TryHackMe)](https://tryhackme.com/room/cryptographyfordummies)

View File

@@ -40,3 +40,5 @@ The Purple Team bridges the gap between the Blue Team and Red Team, helping to c
- Foster a culture of continuous improvement and collaboration
By investing in Blue, Red, and Purple Team efforts, organizations can achieve a more robust and resilient security posture, capable of withstanding and adapting to ever-evolving threats.
- [Red Team Fundamentals (TryHackMe)](https://tryhackme.com/room/redteamfundamentals)

View File

@@ -18,3 +18,5 @@ Professionals working in digital forensics need a solid understanding of various
- Effective communication abilities to convey technical findings to non-technical stakeholders
Overall, digital forensics is a crucial component of cybersecurity as it helps organizations respond effectively to cyber attacks, identify vulnerabilities, and take appropriate steps to safeguard their digital assets.
- [Introduction to Digital Forensics (TryHackMe)](https://tryhackme.com/room/introdigitalforensics)

View File

@@ -43,3 +43,5 @@ Exploit frameworks are essential tools in the cybersecurity landscape, as they p
- Offers USB-based exploitation for human-interface devices
When using these exploit frameworks, it is important to remember that they are powerful tools that can cause significant damage if misused. Always ensure that you have explicit permission from the target organization before conducting any penetration testing activities.
- [Metasploit Primer (TryHackMe)](https://tryhackme.com/room/rpmetasploit)

View File

@@ -47,3 +47,6 @@ Smishing, or SMS phishing, is the act of using text messages to deceive victims
- Install mobile security software to protect your device from potential threats
By staying informed about these various attack types, you can better protect yourself and your organization from falling victim to cyber threats. Remain vigilant and ensure you have proper security measures in place to minimize the risk of these attacks.
- [What is Phishing?](https://www.phishing.org/what-is-phishing)
- [Phishing Examples](https://www.phishing.org/phishing-examples)

View File

@@ -17,3 +17,5 @@ Parrot OS, also known as Parrot Security OS, is a powerful Linux-based distribut
- **Reverse Engineering**: The OS also includes tools for reverse engineering, assisting security professionals in examining and analyzing software or malware designs.
Overall, Parrot OS is a reliable, versatile, and user-friendly cyber security distribution, ideal for both beginners and advanced users engaged in ethical hacking, penetration testing, and digital forensics.
- [Link to Download Parrot OS ](https://www.parrotsec.org/download/)

View File

@@ -35,3 +35,5 @@ Achieving a CompTIA A+ certification can offer several benefits, such as:
- Serving as a prerequisite for more advanced certifications, such as CompTIA Network+ and CompTIA Security+
Overall, if you're an aspiring IT professional, the CompTIA A+ certification is a great starting point to kick off your IT career and begin acquiring the skills and knowledge needed to thrive in this ever-evolving industry.
- [CompTIA A+ 220-1101 - Professor Messer](https://www.youtube.com/watch?v=87t6p5zhtp0&list=plg49s3nxzannomvg5ugvenb_qqgsh01uc&index=1)

View File

@@ -1 +1,3 @@
# Bash scripting
# Bash scripting
- [Bash Scripting Tutorial](https://www.freecodecamp.org/news/bash-scripting-tutorial-linux-shell-script-and-command-line-for-beginners/)

View File

@@ -1 +1,3 @@
# Powershell
# Powershell
- [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/)

View File

@@ -7,6 +7,7 @@ Here are some of the resources to learn about SSH:
- [SSH Intro](https://www.baeldung.com/cs/ssh-intro)
- [What is SSH?](https://www.ssh.com/academy/ssh/protocol)
- [SFTP using SSH](https://www.goanywhere.com/blog/how-sftp-works)
- [OpenSSH Full Guide](https://www.youtube.com/watch?v=ys5zh7kexve)
Visit the following to learn about SSL/TLS:
@@ -32,6 +33,4 @@ Here are some resources to learn about DNS:
- [What is DNS?](https://www.cloudflare.com/en-gb/learning/dns/what-is-dns/)
- [HOw DNS works (comic)](https://howdns.works/)
- [DNS and How does it Work?](https://www.youtube.com/watch?v=Wj0od2ag5sk)
- [DNS Records](https://www.youtube.com/watch?v=7lxgpKh_fRY)
- [DNS Records](https://www.youtube.com/watch?v=7lxgpKh_fRY)

View File

@@ -1 +1,3 @@
# Azure functions
# Azure functions
- [Azure Functions Overview](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview)

View File

@@ -1 +1,6 @@
# Nexus
# Nexus
- [Repository Management Basics](https://learn.sonatype.com/courses/nxrm-admin-100/)
- [Nexus Installation and Configuration](https://learn.sonatype.com/courses/nxrm-config-100/)
- [Nexus Repository Security Essentials](https://learn.sonatype.com/courses/nxrm-sec-100/)
- [Nexus Best Practices](https://help.sonatype.com/repomanager3/nexus-repository-best-practices)

View File

@@ -1536,7 +1536,7 @@
"x": "179",
"y": "304",
"properties": {
"controlName": "ext_link:kodekloud.com?aff=kamranahmed.se"
"controlName": "ext_link:kodekloud.com/learning-path-devops-basics?aff=kamranahmed.se"
},
"children": {
"controls": {
@@ -1551,7 +1551,7 @@
"y": "0",
"properties": {
"size": "17",
"text": "KodeCloud DevOps Courses"
"text": "KodeKloud DevOps Courses"
}
},
{

View File

@@ -16,7 +16,7 @@ schema:
description: 'Learn to become a DevOps, SRE or get any other operations role with this interactive step by step guide in 2023. We also have resources and short descriptions attached to the roadmap items so you can get everything you want to learn in one place.'
imageUrl: 'https://roadmap.sh/roadmaps/devops.png'
datePublished: '2023-01-05'
dateModified: '2023-01-20'
dateModified: '2023-06-10'
seo:
title: 'DevOps Roadmap: Learn to become a DevOps Engineer or SRE'
description: 'Community driven, articles, resources, guides, interview questions, quizzes for DevOps. Learn to become a modern DevOps engineer by following the steps, skills, resources and guides listed in this roadmap.'

View File

@@ -13,4 +13,6 @@ Unlike traditional virtualization, which emulates a complete operating system wi
## Containers and Docker
Docker is a platform that simplifies the process of creating, deploying, and managing containers. It provides developers and administrators with a set of tools and APIs to manage containerized applications. With Docker, you can build and package application code, libraries, and dependencies into a container image, which can be distributed and run consistently in any environment that supports Docker.
Docker is a platform that simplifies the process of creating, deploying, and managing containers. It provides developers and administrators with a set of tools and APIs to manage containerized applications. With Docker, you can build and package application code, libraries, and dependencies into a container image, which can be distributed and run consistently in any environment that supports Docker.
- [What is a container?](https://www.docker.com/resources/what-container/)

View File

@@ -6,7 +6,7 @@ briefTitle: 'Docker'
briefDescription: 'Step by step guide to learning Docker in 2023'
title: 'Docker Roadmap'
description: 'Step by step guide to learning Docker in 2023'
isNew: true
isNew: false
hasTopics: true
dimensions:
width: 968

View File

@@ -1 +1,3 @@
# Inherited Widgets
- [InheritedWidget Official Guide](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html)

View File

@@ -1 +1,4 @@
# Responsive Widgets
- [Official flutter responsive widget ](https://docs.flutter.dev/ui/layout/adaptive-responsive)
- [Creating responsive and adaptive apps](https://docs.flutter.dev/ui/layout/adaptive-responsive)

View File

@@ -18,3 +18,4 @@ Learn more from the following links:
- [Flutter - Design](https://dart.dev/guides/language/effective-dart/design)
- [Design Patterns Explained in 10 Minutes](https://www.youtube.com/watch?v=tv-_1er1mWI)
- [Cookbook Designs in Flutter](https://docs.flutter.dev/cookbook/design)
- [Flutter Design Patterns](https://www.youtube.com/watch?v=sk5hwzfndqs&list=pllzmawv2ytgb-1ldoo-9vctgre-1dywkp&index=1)

View File

@@ -6,7 +6,7 @@ briefTitle: 'Flutter'
briefDescription: 'Step by step guide to becoming a Flutter Developer in 2023'
title: 'Flutter Developer'
description: 'Step by step guide to becoming a Flutter developer in 2023'
isNew: true
isNew: false
hasTopics: true
dimensions:
width: 968

View File

@@ -10,3 +10,4 @@ Visit the following resources to learn more:
- [The guide to responsive web design in 2022](https://webflow.com/blog/responsive-web-design)
- [5 simple tips to making responsive layouts the easy way](https://www.youtube.com/watch?v=VQraviuwbzU)
- [Introduction To Responsive Web Design](https://www.youtube.com/watch?v=srvUrASNj0s)
- [Useful & Responsive Layouts, no Media Queries required](https://youtu.be/p3_xn2zp1ty)

View File

@@ -7,3 +7,4 @@ Visit the following resources to learn more:
- [Esbuild Official Website](https://esbuild.github.io/)
- [Esbuild Documentation](https://esbuild.github.io/api/)
- [Why are People Obsessed with esbuild?](https://www.youtube.com/watch?v=9XS_RA6zyyU)
- [What Is ESBuild?](https://www.youtube.com/watch?v=zy8vu8cbwf0)

View File

@@ -7,3 +7,4 @@ To use the Notifications API, a web page must first request permission from the
Visit the following resources to learn more:
- [Notifications API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API)
- [Create React Notifications With the Web Notifications API](https://www.youtube.com/watch?v=mfrppinfmz0)

File diff suppressed because it is too large Load Diff

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