mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-12 12:13:58 +02:00
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>
This commit is contained in:
@@ -5,6 +5,7 @@ import tailwind from '@astrojs/tailwind';
|
|||||||
import compress from 'astro-compress';
|
import compress from 'astro-compress';
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
import rehypeExternalLinks from 'rehype-external-links';
|
import rehypeExternalLinks from 'rehype-external-links';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
@@ -45,6 +46,22 @@ export default defineConfig({
|
|||||||
format: 'file',
|
format: 'file',
|
||||||
},
|
},
|
||||||
integrations: [
|
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({
|
tailwind({
|
||||||
config: {
|
config: {
|
||||||
applyBaseStyles: false,
|
applyBaseStyles: false,
|
||||||
|
@@ -26,8 +26,8 @@
|
|||||||
"@astrojs/tailwind": "^3.1.3",
|
"@astrojs/tailwind": "^3.1.3",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.4.1",
|
"@fingerprintjs/fingerprintjs": "^3.4.1",
|
||||||
"@nanostores/preact": "^0.5.0",
|
"@nanostores/preact": "^0.5.0",
|
||||||
"astro": "^2.5.7",
|
"astro": "^2.6.3",
|
||||||
"astro-compress": "^1.1.46",
|
"astro-compress": "^1.1.47",
|
||||||
"jose": "^4.14.4",
|
"jose": "^4.14.4",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"nanostores": "^0.9.1",
|
"nanostores": "^0.9.1",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"tailwindcss": "^3.3.2"
|
"tailwindcss": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.34.3",
|
"@playwright/test": "^1.35.0",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/js-cookie": "^3.0.3",
|
"@types/js-cookie": "^3.0.3",
|
||||||
"csv-parser": "^3.0.0",
|
"csv-parser": "^3.0.0",
|
||||||
|
85
pnpm-lock.yaml
generated
85
pnpm-lock.yaml
generated
@@ -6,11 +6,11 @@ specifiers:
|
|||||||
'@astrojs/tailwind': ^3.1.3
|
'@astrojs/tailwind': ^3.1.3
|
||||||
'@fingerprintjs/fingerprintjs': ^3.4.1
|
'@fingerprintjs/fingerprintjs': ^3.4.1
|
||||||
'@nanostores/preact': ^0.5.0
|
'@nanostores/preact': ^0.5.0
|
||||||
'@playwright/test': ^1.34.3
|
'@playwright/test': ^1.35.0
|
||||||
'@tailwindcss/typography': ^0.5.9
|
'@tailwindcss/typography': ^0.5.9
|
||||||
'@types/js-cookie': ^3.0.3
|
'@types/js-cookie': ^3.0.3
|
||||||
astro: ^2.5.7
|
astro: ^2.6.3
|
||||||
astro-compress: ^1.1.46
|
astro-compress: ^1.1.47
|
||||||
csv-parser: ^3.0.0
|
csv-parser: ^3.0.0
|
||||||
gh-pages: ^5.0.0
|
gh-pages: ^5.0.0
|
||||||
jose: ^4.14.4
|
jose: ^4.14.4
|
||||||
@@ -32,11 +32,11 @@ specifiers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/preact': 2.2.1_preact@10.15.1
|
'@astrojs/preact': 2.2.1_preact@10.15.1
|
||||||
'@astrojs/sitemap': 1.3.3
|
'@astrojs/sitemap': 1.3.3
|
||||||
'@astrojs/tailwind': 3.1.3_cyxi2rbbvaq22julk2hkhgjf7u
|
'@astrojs/tailwind': 3.1.3_v3vh2dd4sag23uewccz5c4ppyu
|
||||||
'@fingerprintjs/fingerprintjs': 3.4.1
|
'@fingerprintjs/fingerprintjs': 3.4.1
|
||||||
'@nanostores/preact': 0.5.0_m2wbkjxz7237icvaxqi7ignbgm
|
'@nanostores/preact': 0.5.0_m2wbkjxz7237icvaxqi7ignbgm
|
||||||
astro: 2.5.7
|
astro: 2.6.3
|
||||||
astro-compress: 1.1.46
|
astro-compress: 1.1.47
|
||||||
jose: 4.14.4
|
jose: 4.14.4
|
||||||
js-cookie: 3.0.5
|
js-cookie: 3.0.5
|
||||||
nanostores: 0.9.1
|
nanostores: 0.9.1
|
||||||
@@ -48,7 +48,7 @@ dependencies:
|
|||||||
tailwindcss: 3.3.2
|
tailwindcss: 3.3.2
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@playwright/test': 1.34.3
|
'@playwright/test': 1.35.0
|
||||||
'@tailwindcss/typography': 0.5.9_tailwindcss@3.3.2
|
'@tailwindcss/typography': 0.5.9_tailwindcss@3.3.2
|
||||||
'@types/js-cookie': 3.0.3
|
'@types/js-cookie': 3.0.3
|
||||||
csv-parser: 3.0.0
|
csv-parser: 3.0.0
|
||||||
@@ -74,13 +74,13 @@ packages:
|
|||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/compiler/1.4.2:
|
|
||||||
resolution: {integrity: sha512-xoRp7JpiMZPK/beUcZEM5kM44Z/h20wwwQcl54duPqQMyySG9vZ5xMM6dYiQmn7b3XzpZs0cT6TRDoJJ5gwHAQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@astrojs/compiler/1.5.0:
|
/@astrojs/compiler/1.5.0:
|
||||||
resolution: {integrity: sha512-k04X/7nlMklU0HQUScxbCTf5n8/Vr+0U0bawb9QWulWxd6qJf3FmBrNATgTYiltjB4pc5HBqmmttAfFi7m4lLg==}
|
resolution: {integrity: sha512-k04X/7nlMklU0HQUScxbCTf5n8/Vr+0U0bawb9QWulWxd6qJf3FmBrNATgTYiltjB4pc5HBqmmttAfFi7m4lLg==}
|
||||||
|
|
||||||
|
/@astrojs/internal-helpers/0.1.0:
|
||||||
|
resolution: {integrity: sha512-OSwvoFkTqVowiyP+codQeQZWoq/HOwY32x17NxDglWoCx2sdyXzplDZoVV4/3odmSEY6/A+48WMl5qkjmP1CXw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@astrojs/language-server/1.0.0:
|
/@astrojs/language-server/1.0.0:
|
||||||
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
|
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -101,13 +101,13 @@ packages:
|
|||||||
vscode-uri: 3.0.7
|
vscode-uri: 3.0.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/markdown-remark/2.2.1_astro@2.5.7:
|
/@astrojs/markdown-remark/2.2.1_astro@2.6.3:
|
||||||
resolution: {integrity: sha512-VF0HRv4GpC1XEMLnsKf6jth7JSmlt9qpqP0josQgA2eSpCIAC/Et+y94mgdBIZVBYH/yFnMoIxgKVe93xfO2GA==}
|
resolution: {integrity: sha512-VF0HRv4GpC1XEMLnsKf6jth7JSmlt9qpqP0josQgA2eSpCIAC/Et+y94mgdBIZVBYH/yFnMoIxgKVe93xfO2GA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
astro: ^2.5.0
|
astro: ^2.5.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/prism': 2.1.2
|
'@astrojs/prism': 2.1.2
|
||||||
astro: 2.5.7
|
astro: 2.6.3
|
||||||
github-slugger: 1.5.0
|
github-slugger: 1.5.0
|
||||||
import-meta-resolve: 2.2.2
|
import-meta-resolve: 2.2.2
|
||||||
rehype-raw: 6.1.1
|
rehype-raw: 6.1.1
|
||||||
@@ -154,14 +154,14 @@ packages:
|
|||||||
zod: 3.21.4
|
zod: 3.21.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/tailwind/3.1.3_cyxi2rbbvaq22julk2hkhgjf7u:
|
/@astrojs/tailwind/3.1.3_v3vh2dd4sag23uewccz5c4ppyu:
|
||||||
resolution: {integrity: sha512-10S1omrv5K5HRVAZ0fBgN5vQykn2HRL332LAVFyBASMn1Ff6gDfSK+CPUeUu94eZUOEaPnECLK8EHAqZ8iY9CA==}
|
resolution: {integrity: sha512-10S1omrv5K5HRVAZ0fBgN5vQykn2HRL332LAVFyBASMn1Ff6gDfSK+CPUeUu94eZUOEaPnECLK8EHAqZ8iY9CA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
astro: ^2.5.0
|
astro: ^2.5.0
|
||||||
tailwindcss: ^3.0.24
|
tailwindcss: ^3.0.24
|
||||||
dependencies:
|
dependencies:
|
||||||
'@proload/core': 0.3.3
|
'@proload/core': 0.3.3
|
||||||
astro: 2.5.7
|
astro: 2.6.3
|
||||||
autoprefixer: 10.4.14_postcss@8.4.23
|
autoprefixer: 10.4.14_postcss@8.4.23
|
||||||
postcss: 8.4.23
|
postcss: 8.4.23
|
||||||
postcss-load-config: 4.0.1_postcss@8.4.23
|
postcss-load-config: 4.0.1_postcss@8.4.23
|
||||||
@@ -968,13 +968,13 @@ packages:
|
|||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@playwright/test/1.34.3:
|
/@playwright/test/1.35.0:
|
||||||
resolution: {integrity: sha512-zPLef6w9P6T/iT6XDYG3mvGOqOyb6eHaV9XtkunYs0+OzxBtrPAAaHotc0X+PJ00WPPnLfFBTl7mf45Mn8DBmw==}
|
resolution: {integrity: sha512-6qXdd5edCBynOwsz1YcNfgX8tNWeuS9fxy5o59D0rvHXxRtjXRebB4gE4vFVfEMXl/z8zTnAzfOs7aQDEs8G4Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.16.3
|
'@types/node': 18.16.3
|
||||||
playwright-core: 1.34.3
|
playwright-core: 1.35.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1330,21 +1330,21 @@ packages:
|
|||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/astro-compress/1.1.46:
|
/astro-compress/1.1.47:
|
||||||
resolution: {integrity: sha512-5PiZsCOlYO04jATC/XlpdMT6869QLsbpSNoihutUxkdWpDpoXwNMyxxXsL7QljdXHyFg6XmTDpxOODxvqerVWA==}
|
resolution: {integrity: sha512-UBhhDfZffcPVdDFA4v42G68BdcNL6ZlYoXZdHTappiLWDvZPlhRmK6iuwya3SAGl4La4kqKCfNSqwKbG3agdBQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/csso': 5.0.0
|
'@types/csso': 5.0.0
|
||||||
'@types/html-minifier-terser': 7.0.0
|
'@types/html-minifier-terser': 7.0.0
|
||||||
csso: 5.0.5
|
csso: 5.0.5
|
||||||
files-pipe: 0.0.6
|
files-pipe: 0.0.7
|
||||||
html-minifier-terser: 7.2.0
|
html-minifier-terser: 7.2.0
|
||||||
sharp: 0.32.1
|
sharp: 0.32.1
|
||||||
svgo: 3.0.2
|
svgo: 3.0.2
|
||||||
terser: 5.17.6
|
terser: 5.17.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/astro/2.5.7:
|
/astro/2.6.3:
|
||||||
resolution: {integrity: sha512-qYKMIN4tXAOAsm10vU4f+Q7LfC05JmEbQiJmSBqIEhp+wnQcEUFkGLrHMSsps3oBzMtjErUdDDW5tGJcn5eVlA==}
|
resolution: {integrity: sha512-gJoBX+t11qAeYDjterGEaQZx7qqlGOigWhE0zkcEBv4zy7l4IpKx6nGGUNzD/l8x6WomZxkdZROsOsiEaWSJmQ==}
|
||||||
engines: {node: '>=16.12.0', npm: '>=6.14.0'}
|
engines: {node: '>=16.12.0', npm: '>=6.14.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1353,17 +1353,18 @@ packages:
|
|||||||
sharp:
|
sharp:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.4.2
|
'@astrojs/compiler': 1.5.0
|
||||||
|
'@astrojs/internal-helpers': 0.1.0
|
||||||
'@astrojs/language-server': 1.0.0
|
'@astrojs/language-server': 1.0.0
|
||||||
'@astrojs/markdown-remark': 2.2.1_astro@2.5.7
|
'@astrojs/markdown-remark': 2.2.1_astro@2.6.3
|
||||||
'@astrojs/telemetry': 2.1.1
|
'@astrojs/telemetry': 2.1.1
|
||||||
'@astrojs/webapi': 2.2.0
|
'@astrojs/webapi': 2.2.0
|
||||||
'@babel/core': 7.22.1
|
'@babel/core': 7.22.1
|
||||||
'@babel/generator': 7.21.5
|
'@babel/generator': 7.22.3
|
||||||
'@babel/parser': 7.21.5
|
'@babel/parser': 7.22.4
|
||||||
'@babel/plugin-transform-react-jsx': 7.21.5_@babel+core@7.22.1
|
'@babel/plugin-transform-react-jsx': 7.21.5_@babel+core@7.22.1
|
||||||
'@babel/traverse': 7.21.5
|
'@babel/traverse': 7.22.4
|
||||||
'@babel/types': 7.21.5
|
'@babel/types': 7.22.4
|
||||||
'@types/babel__core': 7.20.0
|
'@types/babel__core': 7.20.0
|
||||||
'@types/yargs-parser': 21.0.0
|
'@types/yargs-parser': 21.0.0
|
||||||
acorn: 8.8.2
|
acorn: 8.8.2
|
||||||
@@ -1374,7 +1375,7 @@ packages:
|
|||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
deepmerge-ts: 4.3.0
|
deepmerge-ts: 4.3.0
|
||||||
devalue: 4.3.0
|
devalue: 4.3.2
|
||||||
diff: 5.1.0
|
diff: 5.1.0
|
||||||
es-module-lexer: 1.2.1
|
es-module-lexer: 1.2.1
|
||||||
esbuild: 0.17.18
|
esbuild: 0.17.18
|
||||||
@@ -2056,8 +2057,8 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/devalue/4.3.0:
|
/devalue/4.3.2:
|
||||||
resolution: {integrity: sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==}
|
resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/didyoumean/1.2.2:
|
/didyoumean/1.2.2:
|
||||||
@@ -2316,8 +2317,8 @@ packages:
|
|||||||
trim-repeated: 1.0.0
|
trim-repeated: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/files-pipe/0.0.6:
|
/files-pipe/0.0.7:
|
||||||
resolution: {integrity: sha512-izHIHcqKEM0V2nw3FvzAqL0nx3IiZ2RC7k4eczIhlJ4X5JWJsxVl4c6b+Uid3zvNd6RiPxjFerbFcodFXMPHBw==}
|
resolution: {integrity: sha512-BkXQoAEo1X3RqmEucIBLL1ddc/LM3lqsf5ipIf/bRrUTn3SQZWhThgpIl1/ma2VNesB2hBoY+CB0rrZ3b1tQhA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
deepmerge-ts: 5.1.0
|
deepmerge-ts: 5.1.0
|
||||||
fast-glob: 3.2.12
|
fast-glob: 3.2.12
|
||||||
@@ -2790,7 +2791,7 @@ packages:
|
|||||||
entities: 4.5.0
|
entities: 4.5.0
|
||||||
param-case: 3.0.4
|
param-case: 3.0.4
|
||||||
relateurl: 0.2.7
|
relateurl: 0.2.7
|
||||||
terser: 5.17.6
|
terser: 5.17.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/html-void-elements/2.0.1:
|
/html-void-elements/2.0.1:
|
||||||
@@ -4446,9 +4447,9 @@ packages:
|
|||||||
find-up: 3.0.0
|
find-up: 3.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/playwright-core/1.34.3:
|
/playwright-core/1.35.0:
|
||||||
resolution: {integrity: sha512-2pWd6G7OHKemc5x1r1rp8aQcpvDh7goMBZlJv6Co5vCNLVcQJdhxRL09SGaY6HcyHH9aT4tiynZabMofVasBYw==}
|
resolution: {integrity: sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -5486,8 +5487,8 @@ packages:
|
|||||||
yallist: 4.0.0
|
yallist: 4.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/terser/5.17.6:
|
/terser/5.17.7:
|
||||||
resolution: {integrity: sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==}
|
resolution: {integrity: sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import CheckIcon from '../../icons/roadmap.svg';
|
import RoadmapIcon from '../../icons/roadmap.svg';
|
||||||
|
|
||||||
export function EmptyActivity() {
|
export function EmptyActivity() {
|
||||||
return (
|
return (
|
||||||
@@ -6,7 +6,7 @@ export function EmptyActivity() {
|
|||||||
<div class="flex flex-col items-center p-7 text-center">
|
<div class="flex flex-col items-center p-7 text-center">
|
||||||
<img
|
<img
|
||||||
alt="no roadmaps"
|
alt="no roadmaps"
|
||||||
src={CheckIcon}
|
src={RoadmapIcon}
|
||||||
class="mb-2 w-[60px] h-[60px] sm:h-[120px] sm:w-[120px] opacity-10"
|
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>
|
<h2 class="text-lg sm:text-xl font-bold">No Progress</h2>
|
||||||
|
20
src/components/HeroSection/CheckIcon.tsx
Normal file
20
src/components/HeroSection/CheckIcon.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
23
src/components/HeroSection/EmptyProgress.tsx
Normal file
23
src/components/HeroSection/EmptyProgress.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { CheckIcon } from './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>
|
||||||
|
);
|
||||||
|
}
|
105
src/components/HeroSection/FavoriteRoadmaps.tsx
Normal file
105
src/components/HeroSection/FavoriteRoadmaps.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(false);
|
||||||
|
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);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
renderProgress(progressList);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
showProgressContainer();
|
||||||
|
loadProgress().finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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 />}
|
||||||
|
{isLoading && <EmptyProgress title="Loading progress .." />}
|
||||||
|
{!isLoading && progress.length > 0 && (
|
||||||
|
<ProgressList progress={progress} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
28
src/components/HeroSection/HeroSection.astro
Normal file
28
src/components/HeroSection/HeroSection.astro
Normal 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:only client:authenticated />
|
||||||
|
</div>
|
46
src/components/HeroSection/ProgressList.tsx
Normal file
46
src/components/HeroSection/ProgressList.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import type { UserProgressResponse } from './FavoriteRoadmaps';
|
||||||
|
import { CheckIcon } from './CheckIcon';
|
||||||
|
|
||||||
|
type ProgressListProps = {
|
||||||
|
progress: UserProgressResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProgressList(props: ProgressListProps) {
|
||||||
|
const { progress } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative pt-4 sm:pt-7 pb-12">
|
||||||
|
<p className="mb-4 flex items-center text-sm text-gray-400">
|
||||||
|
<CheckIcon additionalClasses={'mr-1.5 w-[14px] h-[14px]'} />
|
||||||
|
<span className='hidden sm:inline'>Your progress and favorite roadmaps.</span>
|
||||||
|
<span className='inline sm:hidden'>Your progress and favorite roadmaps.</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2">
|
||||||
|
{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
|
||||||
|
href={url}
|
||||||
|
className="relative flex flex-col rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300 overflow-hidden"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -29,6 +29,18 @@ export function PageSponsor(props: PageSponsorProps) {
|
|||||||
const [sponsor, setSponsor] = useState<PageSponsorType>();
|
const [sponsor, setSponsor] = useState<PageSponsorType>();
|
||||||
|
|
||||||
const loadSponsor = async () => {
|
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>(
|
const { response, error } = await httpGet<V1GetSponsorResponse>(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-sponsor`,
|
`${import.meta.env.PUBLIC_API_URL}/v1-get-sponsor`,
|
||||||
{
|
{
|
||||||
|
@@ -39,10 +39,10 @@ The third stage is converting the compiler's intermediate representation into as
|
|||||||
|
|
||||||
**Code Example (x86 Assembly):**
|
**Code Example (x86 Assembly):**
|
||||||
|
|
||||||
```assembly
|
```
|
||||||
mov eax, 10
|
mov eax, 10
|
||||||
mov ebx, 20
|
mov ebx, 20
|
||||||
add eax, ebx
|
add eax, ebx
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linking
|
## Linking
|
||||||
|
9
src/directives/client-authenticated.mjs
Normal file
9
src/directives/client-authenticated.mjs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export default async (load, opts) => {
|
||||||
|
const isAuthenticated = document.cookie.toString().indexOf('__roadmapsh_jt__') !== -1;
|
||||||
|
if (isAuthenticated) {
|
||||||
|
console.log("loading");
|
||||||
|
|
||||||
|
const hydrate = await load();
|
||||||
|
await hydrate();
|
||||||
|
}
|
||||||
|
};
|
@@ -1,48 +0,0 @@
|
|||||||
import { httpGet } from './http';
|
|
||||||
import { isLoggedIn } from './jwt';
|
|
||||||
|
|
||||||
type UserProgressResponse = {
|
|
||||||
resourceId: string;
|
|
||||||
resourceType: 'roadmap' | 'best-practice';
|
|
||||||
done: number;
|
|
||||||
learning: number;
|
|
||||||
skipped: number;
|
|
||||||
total: number;
|
|
||||||
updatedAt: Date;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
async function renderProgress() {
|
|
||||||
if (!isLoggedIn()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { response: progressList, error } = await httpGet<UserProgressResponse>(
|
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-all-progress`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error || !progressList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalDone = progress.done + progress.skipped;
|
|
||||||
const percentageDone = (totalDone / progress.total) * 100;
|
|
||||||
|
|
||||||
const progressBar: HTMLElement = element.querySelector('[data-progress]')!;
|
|
||||||
progressBar.style.width = `${percentageDone}%`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// on DOM load
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.setTimeout(renderProgress, 0);
|
|
||||||
});
|
|
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
|
import FeaturedVideos from '../components/FeaturedVideos.astro';
|
||||||
import FeaturedGuides from '../components/FeaturedGuides.astro';
|
import FeaturedGuides from '../components/FeaturedGuides.astro';
|
||||||
import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
|
import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
|
||||||
import FeaturedVideos from '../components/FeaturedVideos.astro';
|
import HeroSection from '../components/HeroSection/HeroSection.astro';
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import { getAllBestPractices } from '../lib/best-pratice';
|
import { getAllBestPractices } from '../lib/best-pratice';
|
||||||
import { getAllGuides } from '../lib/guide';
|
import { getAllGuides } from '../lib/guide';
|
||||||
@@ -22,28 +23,7 @@ const videos = await getAllVideos();
|
|||||||
permalink={'/'}
|
permalink={'/'}
|
||||||
>
|
>
|
||||||
<div class='bg-gradient-to-b from-slate-900 to-black'>
|
<div class='bg-gradient-to-b from-slate-900 to-black'>
|
||||||
<div class='border-b border-b-[#1e293c]'>
|
<HeroSection />
|
||||||
<div
|
|
||||||
class='container px-6 py-6 pb-14 text-left sm:px-0 sm:py-20 sm:text-center'
|
|
||||||
>
|
|
||||||
<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 the 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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FeaturedItems
|
<FeaturedItems
|
||||||
heading='Role based Roadmaps'
|
heading='Role based Roadmaps'
|
||||||
@@ -83,6 +63,4 @@ const videos = await getAllVideos();
|
|||||||
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
|
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src='../lib/home-progress.ts'></script>
|
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
Reference in New Issue
Block a user