Add courses functionality

pull/8467/head
Kamran Ahmed 2 weeks ago
parent 7a00234f9a
commit 0d62847053
  1. 12
      .astro/settings.json
  2. 10
      package.json
  3. 321
      pnpm-lock.yaml
  4. 88
      src/components/RoadmapHeader.astro
  5. 31
      src/pages/[roadmapId]/courses.astro
  6. 2
      src/pages/[roadmapId]/index.astro
  7. 1
      src/pages/[roadmapId]/projects.astro

@ -1,8 +1,8 @@
{
"devToolbar": {
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1743851801172
}
"devToolbar": {
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1743851801172
}
}

@ -32,7 +32,7 @@
},
"dependencies": {
"@astrojs/node": "^8.3.4",
"@astrojs/react": "^3.6.2",
"@astrojs/react": "^4.2.3",
"@astrojs/sitemap": "^3.2.0",
"@fingerprintjs/fingerprintjs": "^4.5.0",
"@microsoft/clarity": "^1.0.0",
@ -42,8 +42,8 @@
"@roadmapsh/editor": "workspace:*",
"@tailwindcss/vite": "^4.1.3",
"@tanstack/react-query": "^5.59.16",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"astro": "^4.16.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
@ -63,10 +63,10 @@
"npm-check-updates": "^17.1.3",
"playwright": "^1.48.0",
"prismjs": "^1.29.0",
"react": "^18.3.1",
"react": "^19.0.0",
"react-calendar-heatmap": "^1.9.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.3.1",
"react-dom": "^19.0.0",
"react-resizable-panels": "^2.1.7",
"react-textarea-autosize": "^8.5.7",
"react-tooltip": "^5.28.0",

@ -12,8 +12,8 @@ importers:
specifier: ^8.3.4
version: 8.3.4(astro@4.16.18(@types/node@18.19.86)(lightningcss@1.29.2)(rollup@4.39.0)(typescript@5.8.3))
'@astrojs/react':
specifier: ^3.6.2
version: 3.6.3(@types/node@18.19.86)(@types/react-dom@18.3.6(@types/react@18.3.20))(@types/react@18.3.20)(lightningcss@1.29.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^4.2.3
version: 4.2.3(@types/node@18.19.86)(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(jiti@2.4.2)(lightningcss@1.29.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tsx@4.19.3)
'@astrojs/sitemap':
specifier: ^3.2.0
version: 3.3.0
@ -25,7 +25,7 @@ importers:
version: 1.0.0
'@nanostores/react':
specifier: ^0.8.0
version: 0.8.4(nanostores@0.11.4)(react@18.3.1)
version: 0.8.4(nanostores@0.11.4)(react@19.1.0)
'@napi-rs/image':
specifier: ^1.9.2
version: 1.9.2
@ -40,13 +40,13 @@ importers:
version: 4.1.3(vite@5.4.17(@types/node@18.19.86)(lightningcss@1.29.2))
'@tanstack/react-query':
specifier: ^5.59.16
version: 5.71.10(react@18.3.1)
version: 5.72.0(react@19.1.0)
'@types/react':
specifier: ^18.3.11
version: 18.3.20
specifier: ^19.0.0
version: 19.1.0
'@types/react-dom':
specifier: ^18.3.1
version: 18.3.6(@types/react@18.3.20)
specifier: ^19.0.0
version: 19.1.1(@types/react@19.1.0)
astro:
specifier: ^4.16.1
version: 4.16.18(@types/node@18.19.86)(lightningcss@1.29.2)(rollup@4.39.0)(typescript@5.8.3)
@ -79,7 +79,7 @@ importers:
version: 3.0.5
lucide-react:
specifier: ^0.452.0
version: 0.452.0(react@18.3.1)
version: 0.452.0(react@19.1.0)
luxon:
specifier: ^3.5.0
version: 3.6.1
@ -105,26 +105,26 @@ importers:
specifier: ^1.29.0
version: 1.30.0
react:
specifier: ^18.3.1
version: 18.3.1
specifier: ^19.0.0
version: 19.1.0
react-calendar-heatmap:
specifier: ^1.9.0
version: 1.10.0(react@18.3.1)
version: 1.10.0(react@19.1.0)
react-confetti:
specifier: ^6.1.0
version: 6.4.0(react@18.3.1)
version: 6.4.0(react@19.1.0)
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
specifier: ^19.0.0
version: 19.1.0(react@19.1.0)
react-resizable-panels:
specifier: ^2.1.7
version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-textarea-autosize:
specifier: ^8.5.7
version: 8.5.9(@types/react@18.3.20)(react@18.3.1)
version: 8.5.9(@types/react@19.1.0)(react@19.1.0)
react-tooltip:
specifier: ^5.28.0
version: 5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 5.28.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
rehype-external-links:
specifier: ^3.0.0
version: 3.0.0
@ -169,7 +169,7 @@ importers:
version: 11.0.5
zustand:
specifier: ^5.0.1
version: 5.0.3(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))
version: 5.0.3(@types/react@19.1.0)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
devDependencies:
'@ai-sdk/google':
specifier: ^1.1.19
@ -206,7 +206,7 @@ importers:
version: 5.0.5
ai:
specifier: ^4.1.51
version: 4.3.2(react@18.3.1)(zod@3.24.2)
version: 4.3.2(react@19.1.0)(zod@3.24.2)
csv-parser:
specifier: ^3.0.0
version: 3.2.0
@ -221,7 +221,7 @@ importers:
version: 14.1.0
openai:
specifier: ^4.67.3
version: 4.91.1(zod@3.24.2)
version: 4.92.0(zod@3.24.2)
prettier:
specifier: ^3.3.3
version: 3.5.3
@ -239,7 +239,7 @@ importers:
dependencies:
'@xyflow/react':
specifier: ^12.4.2
version: 12.5.4(@types/react@19.1.0)(react-dom@18.3.1(react@19.1.0))(react@19.1.0)
version: 12.5.5(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
clsx:
specifier: ^2.1.1
version: 2.1.1
@ -257,7 +257,7 @@ importers:
version: 11.0.0
tailwind-merge:
specifier: ^3.0.1
version: 3.1.0
version: 3.2.0
unified:
specifier: ^11.0.5
version: 11.0.5
@ -347,14 +347,14 @@ packages:
resolution: {integrity: sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==}
engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0}
'@astrojs/react@3.6.3':
resolution: {integrity: sha512-5ihLQDH5Runddug5AZYlnp/Q5T81QxhwnWJXA9rchBAdh11c6UhBbv9Kdk7b2PkXoEU70CGWBP9hSh0VCR58eA==}
engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0}
'@astrojs/react@4.2.3':
resolution: {integrity: sha512-icL1hCnW1v+w+NCAz8REfsh9R1aGMW75fYBoeLjyhrVDxXQHiFbTfyBIHkgH79qqID7SM81+hPxHlqcgCuBP8w==}
engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0}
peerDependencies:
'@types/react': ^17.0.50 || ^18.0.21
'@types/react-dom': ^17.0.17 || ^18.0.6
react: ^17.0.2 || ^18.0.0 || ^19.0.0-beta
react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0-beta
'@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0
'@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0
react: ^17.0.2 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0
'@astrojs/sitemap@3.3.0':
resolution: {integrity: sha512-nYE4lKQtk+Kbrw/w0G0TTgT724co0jUsU4tPlHY9au5HmTBKbwiCLwO/15b1/y13aZ4Kr9ZbMeMHlXuwn0ty4Q==}
@ -1335,11 +1335,11 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6
'@tanstack/query-core@5.71.10':
resolution: {integrity: sha512-/fKEY8fO1nbszfrBatzmhJa1nEwIKn0c6Tv2A1ocSA5OiD2GukOIV8nnBbvJRgZb/VIoBy9/N4PVbABI8YQLow==}
'@tanstack/query-core@5.72.0':
resolution: {integrity: sha512-aa3p6Mou++JLLxxxVX9AB9uGeRIGc0JWkw96GASXuMG8K3D+JpYbSFcqXbkGFJ1eX2jKHPurmCBoO43RjjXJCA==}
'@tanstack/react-query@5.71.10':
resolution: {integrity: sha512-mQYM/ObpL8YMDz8vCoUuHkbe8Yu7NnVRH8aBaBa/3zlufjp1f1VuWjeO3TcumNHfuVMDwEAGinsgwrB7OKADiQ==}
'@tanstack/react-query@5.72.0':
resolution: {integrity: sha512-4Dejq/IiXrPlr/0xxj4H2GbC6KckwfTCoHWbd02+UoIV0laC9yke0d0KegmFdXJA712I6UCuy8WpPM76uuPJ+w==}
peerDependencies:
react: ^18 || ^19
@ -1447,23 +1447,17 @@ packages:
'@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
'@types/react-calendar-heatmap@1.9.0':
resolution: {integrity: sha512-BH8M/nsXoLGa3hxWbrq3guPwlK0cV+w1i4c/ktrTxTzN5fBths6WbeUZ4dK0+tE76qiGoVSo9Tse8WVVuMIV+w==}
'@types/react-dom@18.3.6':
resolution: {integrity: sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==}
'@types/react-dom@19.1.1':
resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==}
peerDependencies:
'@types/react': ^18.0.0
'@types/react': ^19.0.0
'@types/react-slick@0.23.13':
resolution: {integrity: sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==}
'@types/react@18.3.20':
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
'@types/react@19.1.0':
resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==}
@ -1488,14 +1482,14 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0
'@xyflow/react@12.5.4':
resolution: {integrity: sha512-ITCW3h/pXVKwb0j3w8CxPyfmmxIqToctE55R+BcfTAe9j+qr/6Uc+NUsbIgnvEyiziJKq/sS0ovVfZ4482txww==}
'@xyflow/react@12.5.5':
resolution: {integrity: sha512-mAtHuS4ktYBL1ph5AJt7X/VmpzzlmQBN3+OXxyT/1PzxwrVto6AKc3caerfxzwBsg3cA4J8lB63F3WLAuPMmHw==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
'@xyflow/system@0.0.54':
resolution: {integrity: sha512-DBoQTcSQ2620WMfakCcjRLrlqalWcZBPgMNrfSAybnVeyZm73rT1592GAXPcC3eoVmWcvGfBgqwAmmNtlrowdw==}
'@xyflow/system@0.0.55':
resolution: {integrity: sha512-6cngWlE4oMXm+zrsbJxerP3wUNUFJcv/cE5kDfu0qO55OWK3fAeSOLW9td3xEVQlomjIW5knds1MzeMnBeCfqw==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
@ -2777,8 +2771,8 @@ packages:
oniguruma-to-es@4.1.0:
resolution: {integrity: sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==}
openai@4.91.1:
resolution: {integrity: sha512-DbjrR0hIMQFbxz8+3qBsfPJnh3+I/skPgoSlT7f9eiZuhGBUissPQULNgx6gHNkLoZ3uS0uYS6eXPUdtg4nHzw==}
openai@4.92.0:
resolution: {integrity: sha512-vLIBP8gygD5M7XIrdBkUFKnfEq3EmaI+lmGjDDAmjahzmdhwdpzDA+GBA4ZZwj7rgu1WMNh9/SqyTysxMulC2g==}
hasBin: true
peerDependencies:
ws: ^8.18.0
@ -3094,10 +3088,10 @@ packages:
peerDependencies:
react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
peerDependencies:
react: ^18.3.1
react: ^19.1.0
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@ -3124,10 +3118,6 @@ packages:
react: '>=16.14.0'
react-dom: '>=16.14.0'
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
@ -3245,8 +3235,8 @@ packages:
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
section-matter@1.0.0:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
@ -3394,8 +3384,8 @@ packages:
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
tailwind-merge@3.1.0:
resolution: {integrity: sha512-aV27Oj8B7U/tAOMhJsSGdWqelfmudnGMdXIlMnk1JfsjwSjts6o8HyfN7SFH3EztzH4YH8kk6GbLTHzITJO39Q==}
tailwind-merge@3.2.0:
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
tailwindcss@4.1.3:
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
@ -3640,6 +3630,46 @@ packages:
terser:
optional: true
vite@6.2.5:
resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitefu@1.0.6:
resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==}
peerDependencies:
@ -3785,12 +3815,12 @@ snapshots:
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.2.6(react@18.3.1)(zod@3.24.2)':
'@ai-sdk/react@1.2.6(react@19.1.0)(zod@3.24.2)':
dependencies:
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.2)
'@ai-sdk/ui-utils': 1.2.5(zod@3.24.2)
react: 18.3.1
swr: 2.3.3(react@18.3.1)
react: 19.1.0
swr: 2.3.3(react@19.1.0)
throttleit: 2.1.0
optionalDependencies:
zod: 3.24.2
@ -3848,17 +3878,18 @@ snapshots:
dependencies:
prismjs: 1.30.0
'@astrojs/react@3.6.3(@types/node@18.19.86)(@types/react-dom@18.3.6(@types/react@18.3.20))(@types/react@18.3.20)(lightningcss@1.29.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@astrojs/react@4.2.3(@types/node@18.19.86)(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(jiti@2.4.2)(lightningcss@1.29.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tsx@4.19.3)':
dependencies:
'@types/react': 18.3.20
'@types/react-dom': 18.3.6(@types/react@18.3.20)
'@vitejs/plugin-react': 4.3.4(vite@5.4.17(@types/node@18.19.86)(lightningcss@1.29.2))
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@types/react': 19.1.0
'@types/react-dom': 19.1.1(@types/react@19.1.0)
'@vitejs/plugin-react': 4.3.4(vite@6.2.5(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3))
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
ultrahtml: 1.6.0
vite: 5.4.17(@types/node@18.19.86)(lightningcss@1.29.2)
vite: 6.2.5(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
@ -3867,6 +3898,8 @@ snapshots:
- sugarss
- supports-color
- terser
- tsx
- yaml
'@astrojs/sitemap@3.3.0':
dependencies:
@ -4300,10 +4333,10 @@ snapshots:
'@mixmark-io/domino@2.2.0': {}
'@nanostores/react@0.8.4(nanostores@0.11.4)(react@18.3.1)':
'@nanostores/react@0.8.4(nanostores@0.11.4)(react@19.1.0)':
dependencies:
nanostores: 0.11.4
react: 18.3.1
react: 19.1.0
'@napi-rs/image-android-arm64@1.9.2':
optional: true
@ -4657,12 +4690,12 @@ snapshots:
tailwindcss: 4.1.3
vite: 5.4.17(@types/node@18.19.86)(lightningcss@1.29.2)
'@tanstack/query-core@5.71.10': {}
'@tanstack/query-core@5.72.0': {}
'@tanstack/react-query@5.71.10(react@18.3.1)':
'@tanstack/react-query@5.72.0(react@19.1.0)':
dependencies:
'@tanstack/query-core': 5.71.10
react: 18.3.1
'@tanstack/query-core': 5.72.0
react: 19.1.0
'@tiptap/core@2.11.7(@tiptap/pm@2.11.7)':
dependencies:
@ -4797,24 +4830,17 @@ snapshots:
'@types/prismjs@1.26.5': {}
'@types/prop-types@15.7.14': {}
'@types/react-calendar-heatmap@1.9.0':
dependencies:
'@types/react': 18.3.20
'@types/react': 19.1.0
'@types/react-dom@18.3.6(@types/react@18.3.20)':
'@types/react-dom@19.1.1(@types/react@19.1.0)':
dependencies:
'@types/react': 18.3.20
'@types/react': 19.1.0
'@types/react-slick@0.23.13':
dependencies:
'@types/react': 18.3.20
'@types/react@18.3.20':
dependencies:
'@types/prop-types': 15.7.14
csstype: 3.1.3
'@types/react': 19.1.0
'@types/react@19.1.0':
dependencies:
@ -4834,29 +4860,29 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.3.4(vite@5.4.17(@types/node@18.19.86)(lightningcss@1.29.2))':
'@vitejs/plugin-react@4.3.4(vite@6.2.5(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3))':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.17(@types/node@18.19.86)(lightningcss@1.29.2)
vite: 6.2.5(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)
transitivePeerDependencies:
- supports-color
'@xyflow/react@12.5.4(@types/react@19.1.0)(react-dom@18.3.1(react@19.1.0))(react@19.1.0)':
'@xyflow/react@12.5.5(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@xyflow/system': 0.0.54
'@xyflow/system': 0.0.55
classcat: 5.0.5
react: 19.1.0
react-dom: 18.3.1(react@19.1.0)
react-dom: 19.1.0(react@19.1.0)
zustand: 4.5.6(@types/react@19.1.0)(react@19.1.0)
transitivePeerDependencies:
- '@types/react'
- immer
'@xyflow/system@0.0.54':
'@xyflow/system@0.0.55':
dependencies:
'@types/d3-drag': 3.0.7
'@types/d3-selection': 3.0.11
@ -4876,17 +4902,17 @@ snapshots:
dependencies:
humanize-ms: 1.2.1
ai@4.3.2(react@18.3.1)(zod@3.24.2):
ai@4.3.2(react@19.1.0)(zod@3.24.2):
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.2)
'@ai-sdk/react': 1.2.6(react@18.3.1)(zod@3.24.2)
'@ai-sdk/react': 1.2.6(react@19.1.0)(zod@3.24.2)
'@ai-sdk/ui-utils': 1.2.5(zod@3.24.2)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
zod: 3.24.2
optionalDependencies:
react: 18.3.1
react: 19.1.0
ansi-align@3.0.1:
dependencies:
@ -5878,9 +5904,9 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react@0.452.0(react@18.3.1):
lucide-react@0.452.0(react@19.1.0):
dependencies:
react: 18.3.1
react: 19.1.0
lucide-react@0.474.0(react@19.1.0):
dependencies:
@ -6330,7 +6356,7 @@ snapshots:
regex: 6.0.1
regex-recursion: 6.0.2
openai@4.91.1(zod@3.24.2):
openai@4.92.0(zod@3.24.2):
dependencies:
'@types/node': 18.19.86
'@types/node-fetch': 2.6.12
@ -6615,57 +6641,46 @@ snapshots:
range-parser@1.2.1: {}
react-calendar-heatmap@1.10.0(react@18.3.1):
react-calendar-heatmap@1.10.0(react@19.1.0):
dependencies:
memoize-one: 5.2.1
prop-types: 15.8.1
react: 18.3.1
react: 19.1.0
react-confetti@6.4.0(react@18.3.1):
react-confetti@6.4.0(react@19.1.0):
dependencies:
react: 18.3.1
react: 19.1.0
tween-functions: 1.2.0
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
react: 18.3.1
scheduler: 0.23.2
react-dom@18.3.1(react@19.1.0):
react-dom@19.1.0(react@19.1.0):
dependencies:
loose-envify: 1.4.0
react: 19.1.0
scheduler: 0.23.2
scheduler: 0.26.0
react-is@16.13.1: {}
react-refresh@0.14.2: {}
react-resizable-panels@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-resizable-panels@2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-textarea-autosize@8.5.9(@types/react@18.3.20)(react@18.3.1):
react-textarea-autosize@8.5.9(@types/react@19.1.0)(react@19.1.0):
dependencies:
'@babel/runtime': 7.27.0
react: 18.3.1
use-composed-ref: 1.4.0(@types/react@18.3.20)(react@18.3.1)
use-latest: 1.3.0(@types/react@18.3.20)(react@18.3.1)
react: 19.1.0
use-composed-ref: 1.4.0(@types/react@19.1.0)(react@19.1.0)
use-latest: 1.3.0(@types/react@19.1.0)(react@19.1.0)
transitivePeerDependencies:
- '@types/react'
react-tooltip@5.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-tooltip@5.28.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@floating-ui/dom': 1.6.13
classnames: 2.5.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react@18.3.1:
dependencies:
loose-envify: 1.4.0
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react@19.1.0: {}
@ -6872,9 +6887,7 @@ snapshots:
sax@1.4.1: {}
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
scheduler@0.26.0: {}
section-matter@1.0.0:
dependencies:
@ -7053,15 +7066,15 @@ snapshots:
dependencies:
s.color: 0.0.15
swr@2.3.3(react@18.3.1):
swr@2.3.3(react@19.1.0):
dependencies:
dequal: 2.0.3
react: 18.3.1
use-sync-external-store: 1.5.0(react@18.3.1)
react: 19.1.0
use-sync-external-store: 1.5.0(react@19.1.0)
tailwind-merge@2.6.0: {}
tailwind-merge@3.1.0: {}
tailwind-merge@3.2.0: {}
tailwindcss@4.1.3: {}
@ -7239,28 +7252,24 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
use-composed-ref@1.4.0(@types/react@18.3.20)(react@18.3.1):
use-composed-ref@1.4.0(@types/react@19.1.0)(react@19.1.0):
dependencies:
react: 18.3.1
react: 19.1.0
optionalDependencies:
'@types/react': 18.3.20
'@types/react': 19.1.0
use-isomorphic-layout-effect@1.2.0(@types/react@18.3.20)(react@18.3.1):
use-isomorphic-layout-effect@1.2.0(@types/react@19.1.0)(react@19.1.0):
dependencies:
react: 18.3.1
react: 19.1.0
optionalDependencies:
'@types/react': 18.3.20
'@types/react': 19.1.0
use-latest@1.3.0(@types/react@18.3.20)(react@18.3.1):
use-latest@1.3.0(@types/react@19.1.0)(react@19.1.0):
dependencies:
react: 18.3.1
use-isomorphic-layout-effect: 1.2.0(@types/react@18.3.20)(react@18.3.1)
react: 19.1.0
use-isomorphic-layout-effect: 1.2.0(@types/react@19.1.0)(react@19.1.0)
optionalDependencies:
'@types/react': 18.3.20
use-sync-external-store@1.5.0(react@18.3.1):
dependencies:
react: 18.3.1
'@types/react': 19.1.0
use-sync-external-store@1.5.0(react@19.1.0):
dependencies:
@ -7293,6 +7302,18 @@ snapshots:
fsevents: 2.3.3
lightningcss: 1.29.2
vite@6.2.5(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3):
dependencies:
esbuild: 0.25.2
postcss: 8.5.3
rollup: 4.39.0
optionalDependencies:
'@types/node': 18.19.86
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.29.2
tsx: 4.19.3
vitefu@1.0.6(vite@5.4.17(@types/node@18.19.86)(lightningcss@1.29.2)):
optionalDependencies:
vite: 5.4.17(@types/node@18.19.86)(lightningcss@1.29.2)
@ -7378,12 +7399,6 @@ snapshots:
'@types/react': 19.1.0
react: 19.1.0
zustand@5.0.3(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)):
optionalDependencies:
'@types/react': 18.3.20
react: 18.3.1
use-sync-external-store: 1.5.0(react@18.3.1)
zustand@5.0.3(@types/react@19.1.0)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)):
optionalDependencies:
'@types/react': 19.1.0

@ -28,6 +28,7 @@ export interface Props {
isUpcoming?: boolean;
hasSearch?: boolean;
projectCount?: number;
coursesCount?: number;
question?: RoadmapFrontmatter['question'];
hasTopics?: boolean;
isForkable?: boolean;
@ -45,12 +46,11 @@ const {
projectCount = 0,
question,
activeTab = 'roadmap',
coursesCount = 0,
} = Astro.props;
const roadmapTitle =
roadmapId === 'devops'
? 'DevOps'
: `${roadmapId.charAt(0).toUpperCase()}${roadmapId.slice(1)}`;
const hasCourses = coursesCount > 0;
const hasProjects = projectCount > 0;
---
<LoginPopup />
@ -72,7 +72,7 @@ const roadmapTitle =
}
<div
class='relative rounded-none border bg-white px-5 pb-0 pt-4 sm:rounded-lg'
class='relative rounded-none border bg-white px-5 pt-4 pb-0 sm:rounded-lg'
>
<div class='flex items-start justify-between'>
<a
@ -91,7 +91,7 @@ const roadmapTitle =
&larr;&nbsp;<span>&nbsp;All Roadmaps</span>
</a>
<div
class='relative right-0 top-0 flex items-center gap-1 sm:-right-2 sm:-top-0.5'
class='relative top-0 right-0 flex items-center gap-1 sm:-top-0.5 sm:-right-2'
>
<MarkFavorite
resourceId={roadmapId}
@ -113,51 +113,55 @@ const roadmapTitle =
/>
</div>
</div>
<div class='mb-5 mt-5 sm:mb-12 sm:mt-12'>
<div class:list={['mt-5 mb-5 sm:mt-12 sm:mb-12']}>
<h1 class='mb-0.5 text-2xl font-bold sm:mb-3.5 sm:text-5xl'>
{title}
</h1>
<p class='text-balance text-sm text-gray-500 sm:text-lg'>
<p class='text-sm text-balance text-gray-500 sm:text-lg'>
{description}
</p>
</div>
<div class='flex justify-between gap-2 sm:gap-0'>
<div class='relative top-px flex gap-1 sm:gap-3'>
<TabLink
url={`/${roadmapId}`}
icon={MapIcon}
isActive={activeTab === 'roadmap'}
text='Roadmap'
/>
<TabLink
url={`/${roadmapId}/projects`}
icon={FolderKanbanIcon}
text='Projects'
isActive={activeTab === 'projects'}
badgeText={projectCount > 0 ? '' : 'soon'}
/>
{
roadmapId === 'sql' && (
{
(
<div class='flex justify-between gap-2 sm:gap-0'>
<div class='relative top-px flex gap-1 sm:gap-3'>
<TabLink
url={`/${roadmapId}/courses`}
icon={BookOpenIcon}
text='Courses'
isActive={activeTab === 'courses'}
badgeText='New'
url={`/${roadmapId}`}
icon={MapIcon}
isActive={activeTab === 'roadmap'}
text='Roadmap'
/>
)
}
</div>
{hasProjects && (
<TabLink
url={`/${roadmapId}/projects`}
icon={FolderKanbanIcon}
text='Projects'
isActive={activeTab === 'projects'}
badgeText={projectCount > 0 ? '' : 'soon'}
/>
)}
{hasCourses && (
<TabLink
url={`/${roadmapId}/courses`}
icon={BookOpenIcon}
text='Courses'
isActive={activeTab === 'courses'}
badgeText='New'
/>
)}
</div>
<TabLink
url={`https://github.com/kamranahmedse/developer-roadmap/issues/new/choose`}
icon={MessageCircle}
text='Suggest Changes'
isExternal={true}
hideTextOnMobile={true}
isActive={false}
/>
</div>
<TabLink
url={`https://github.com/kamranahmedse/developer-roadmap/issues/new/choose`}
icon={MessageCircle}
text='Suggest Changes'
isExternal={true}
hideTextOnMobile={true}
isActive={false}
/>
</div>
)
}
</div>
</div>

@ -48,25 +48,7 @@ const nounTitle =
descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle;
const seoDescription = `Seeking ${nounTitle.toLowerCase()} courses to enhance your skills? Explore our top free and paid courses to help you become a ${nounTitle} expert!`;
const courses = [
{
title: 'Complete Course to Master SQL',
description: 'Learn SQL from scratch with this comprehensive course',
link: 'https://roadmap.sh/courses/sql',
instructor: {
name: 'Kamran Ahmed',
image: 'https://github.com/kamranahmedse.png',
title: 'Founder roadmap.sh',
},
features: [
'55+ Lessons',
'AI Tutor',
'Coding Environment',
'Quizzes',
'Certification',
],
},
];
const courses = roadmapData.courses || [];
---
<BaseLayout
@ -91,17 +73,20 @@ const courses = [
isUpcoming={roadmapData.isUpcoming}
isForkable={roadmapData.isForkable}
question={roadmapData.question}
coursesCount={courses.length}
activeTab='courses'
/>
<div class='container'>
<div class='relative mb-8 mt-2.5'>
<div class='relative mt-2.5 mb-8'>
<div class='my-4 flex items-center justify-between'>
<p class='border-l-4 border-black pl-2 text-sm text-black'>
Official Courses by <span class='font-semibold'> roadmap.sh </span>
team
</p>
<div class='text-sm text-gray-500'>More coming soon</div>
<div class='hidden text-sm text-gray-500 sm:block'>
More coming soon
</div>
</div>
<div class='grid grid-cols-1 gap-5 md:grid-cols-1'>
@ -127,7 +112,7 @@ const courses = [
</span>
))}
</div>
<div class='flex items-center justify-between'>
<div class='mt-6 flex flex-col items-start justify-between gap-3 sm:mt-0 sm:flex-row sm:items-center sm:gap-0'>
<div class='flex items-center'>
<img
src={course.instructor.image}
@ -144,7 +129,7 @@ const courses = [
</div>
</div>
<span class='group rounded-lg border border-gray-900 bg-gray-900 px-4 py-1.5 text-sm font-medium text-white transition-colors duration-300 hover:opacity-80'>
<span class='group w-full sm:py-1.5 sm:w-auto mt-3 rounded-lg border border-gray-900 bg-gray-900 px-4 py-2 text-sm font-medium text-white transition-colors duration-300 hover:opacity-80 sm:mt-0'>
View Course <span class='ml-1'>&rarr;</span>
</span>
</div>

@ -70,6 +70,7 @@ const ogImageUrl =
const question = roadmapData?.question;
const note = roadmapData.note;
const projects = await getProjectsByRoadmapId(roadmapId);
const courses = roadmapData.courses || [];
---
<BaseLayout
@ -114,6 +115,7 @@ const projects = await getProjectsByRoadmapId(roadmapId);
isForkable={roadmapData.isForkable}
question={roadmapData.question}
projectCount={projects.length}
coursesCount={courses.length}
/>
<div class='container mt-2.5'>

@ -83,6 +83,7 @@ const { response: userCounts } =
question={roadmapData.question}
activeTab='projects'
projectCount={projects.length}
coursesCount={roadmapData.courses?.length || 0}
/>
<div class='container'>

Loading…
Cancel
Save