Compare commits

..

5 Commits

Author SHA1 Message Date
Arik Chakma
3ceab552f6 feat: project listing 2024-08-28 08:06:28 +06:00
Arik Chakma
e39fadb032 fix: responsiveness share button 2024-08-28 06:32:28 +06:00
Arik Chakma
8f6cdff0d8 fix: step count issue 2024-08-27 10:22:19 +06:00
Arik Chakma
018a8d6f0f feat: share solution modal 2024-08-27 09:51:10 +06:00
Arik Chakma
7da86f173b wip: implement success modal 2024-08-27 07:21:36 +06:00
851 changed files with 9222 additions and 13155 deletions

View File

@@ -3,6 +3,6 @@
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1725962974592
"lastUpdateCheck": 1723855511353
}
}

1
.astro/types.d.ts vendored
View File

@@ -1 +0,0 @@
/// <reference types="astro/client" />

View File

@@ -1,16 +0,0 @@
name: Clears API Cloudfront Cache
on:
workflow_dispatch:
jobs:
aws_costs:
runs-on: ubuntu-latest
steps:
- name: Clear Cloudfront Caching
run: |
curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront-api", "is_verbose": false } }'

View File

@@ -1,4 +1,4 @@
name: Clears Frontend Cloudfront Cache
name: Clears Cloudfront Cache
on:
workflow_dispatch:
jobs:

View File

@@ -1,26 +1,24 @@
name: Deploy to EC2
on:
workflow_dispatch:
workflow_dispatch: # allow manual run
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 2
- uses: actions/setup-node@v4
- uses: actions/setup-node@v1
with:
node-version: 20
- uses: pnpm/action-setup@v4.0.0
- uses: pnpm/action-setup@v3.0.0
with:
version: 9
version: 8.15.6
# -------------------
# --------------------
# Setup configuration
# -------------------
# --------------------
- name: Prepare configuration files
run: |
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/infra-config.git configuration --depth 1
@@ -28,14 +26,13 @@ jobs:
run: |
cp configuration/dist/github/developer-roadmap.env .env
# -----------------
# Prepare the Build
# -----------------
- name: Install Dependencies
# --------------------
# Prepare the build
# --------------------
- name: Install dependencies
run: |
pnpm install
- name: Generate Production Build
- name: Generate build
run: |
git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/web-draw.git .temp/web-draw --depth 1
npm run generate-renderer
@@ -48,7 +45,7 @@ jobs:
- uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.EC2_PRIVATE_KEY }}
- name: Deploy Application to EC2
- name: Deploy app to EC2
run: |
rsync -apvz --delete --no-times --exclude "configuration" -e "ssh -o StrictHostKeyChecking=no" -p ./ ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/var/www/roadmap.sh/
- name: Restart PM2
@@ -61,9 +58,9 @@ jobs:
cd /var/www/roadmap.sh
sudo pm2 restart web-roadmap
# ----------------------
# --------------------
# Clear cloudfront cache
# ----------------------
# --------------------
- name: Clear Cloudfront Caching
run: |
curl -L \

View File

@@ -1,15 +1,13 @@
name: Label Issue
on:
issues:
types: [ opened, edited ]
jobs:
label-topic-change-issue:
runs-on: ubuntu-latest
steps:
- name: Add Labels To Issue
uses: actions/github-script@v7
- name: Add roadmap slug to issue as label
uses: actions/github-script@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
@@ -20,7 +18,7 @@ jobs:
if (issue.labels.some(label => label.name === 'topic-change')) {
if (roadmapUrl) {
const roadmapSlug = new URL(roadmapUrl[0]).pathname.replace(/\//, '');
github.rest.issues.addLabels({
github.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
@@ -30,7 +28,7 @@ jobs:
// Close the issue if it has no roadmap URL
if (!roadmapUrl) {
github.rest.issues.update({
github.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,

View File

@@ -1,35 +1,35 @@
name: Refresh Roadmap Content JSON
name: Refreshes roadmap content JSON
on:
workflow_dispatch:
workflow_dispatch: # allow manual run
schedule:
- cron: '0 0 * * *'
- cron: '0 0 * * *' # every day at midnight
jobs:
refresh-content:
upgrade-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm@v9
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
run_install: false
- name: Setup Node.js Version 20 (LTS)
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 18
cache: 'pnpm'
- name: Install Dependencies and Generate Content JSON
- name: Install dependencies and generate content JSON
run: |
pnpm install
npm run generate:roadmap-content-json
- name: Create PR
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v4
with:
delete-branch: false
branch: "chore/update-content-json"
@@ -37,16 +37,9 @@ jobs:
labels: |
dependencies
automated pr
reviewers: kamranahmedse
reviewers: kamranahmedse,arikchakma
commit-message: "chore: update roadmap content json"
title: "Updated Roadmap Content JSON - Automated"
title: "Update roadmap content json"
body: |
## Updated Roadmap Content JSON
> [!IMPORTANT]
> This PR Updates the Roadmap Content JSON files stored in the `public` directory.
>
> Commit: ${{ github.sha }}
> Workflow Path: ${{ github.workflow_ref }}
**Please Review the Changes and Merge the PR if everything is fine.**
Updates the roadmap content JSON files in the `public` folder.
Please review the changes and merge if everything looks good.

38
.github/workflows/update-deps.yml vendored Normal file
View File

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

View File

@@ -1,51 +0,0 @@
name: Upgrade Dependencies
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'
jobs:
upgrade-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js Version 20 (LTS)
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm@v9
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install & Upgrade Dependencies
run: |
pnpm install
npm run upgrade
pnpm install --lockfile-only
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
delete-branch: false
branch: "update-deps"
base: "master"
labels: |
dependencies
automated pr
reviewers: kamranahmedse
commit-message: "chore: update dependencies to latest"
title: "Upgrade Dependencies To Latest - Automated"
body: |
## Updated all Dependencies to Latest Versions.
> [!IMPORTANT]
> This PR Upgrades the Dependencies to the Latest Their Versions.
>
> Commit: ${{ github.sha }}
> Workflow Path: ${{ github.workflow_ref }}
**Please Review the Changes and Merge the PR if everything is fine.**

View File

@@ -1,6 +1,6 @@
# Contribution
First of all, thank you for considering to contribute. Please look at the details below:
First of all thank you for considering to contribute. Please look at the details below:
- [New Roadmaps](#new-roadmaps)
- [Existing Roadmaps](#existing-roadmaps)
@@ -25,22 +25,22 @@ For the existing roadmaps, please follow the details listed for the nature of co
## Adding Projects
If you have a project idea that you think we should add to the roadmap, feel free to open an issue with as many details about the project as possible and the roadmap you think it should be added to.
If you have a project idea that you think we should add to the roadmap, feel free to open an issue with as much details about the project as possible and the roadmap you think it should be added to.
The detailed format for the issue should be as follows:
The detailed format for issue should be as follows:
```
## What is this project about?
(Add an introduction to the project.)
(Add introduction to the project)
## Skills this Project Covers
(Comma separated list of skills, e.g. Programming Knowledge, Database, etc.)
(Comma separated list of skills e.g. Programming Knowledge, Database,)
## Requirements
( Detailed list of requirements, i.e. input, output, hints to help build this, etc.)
( Detailed list of requirements, i.e. input, output, an hints to help build this etc)
```
Have a look at this project to get an idea of [what we are looking for](https://roadmap.sh/projects/github-user-activity).
@@ -67,7 +67,7 @@ Visit the following resources to learn more:
- [@type@Description of link](Link)
```
`@type@` must be one of the following and describe the type of content you are adding:
`@type@` must be one of the following and describes the type of content you are adding:
- `@official@`
- `@opensource@`
@@ -82,11 +82,11 @@ It's important to add a valid type, this will help us categorize the content and
- <p><strong>Please don't use the project for self-promotion!</strong><br />
We believe this project is a valuable asset to the developer community, and it includes numerous helpful resources. We kindly ask you to avoid submitting pull requests for the sole purpose of self-promotion. We appreciate contributions that genuinely add value, such as guides from maintainers of well-known frameworks, and will consider accepting these even if they're self authored. Thank you for your understanding and cooperation!
We believe this project is a valuable asset to the developer community and it includes numerous helpful resources. We kindly ask you to avoid submitting pull requests for the sole purpose of self-promotion. We appreciate contributions that genuinely add value, such as guides from maintainers of well-known frameworks, and will consider accepting these even if they're self authored. Thank you for your understanding and cooperation!
- <p><strong>Adding everything available out there is not the goal!</strong><br />
The roadmaps represent the skillset most valuable today, i.e., if you were to enter any of the listed fields today, what would you learn? There might be things that are of-course being used today, but prioritize the things that are most in demand today, e.g., agree that lots of people are using angular.js today, but you wouldn't want to learn that instead of React, Angular, or Vue. Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included.</p>
The roadmaps represent the skillset most valuable today, i.e., if you were to enter any of the listed fields today, what would you learn? There might be things that are of-course being used today but prioritize the things that are most in demand today, e.g., agreed that lots of people are using angular.js today but you wouldn't want to learn that instead of React, Angular, or Vue. Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included.</p>
- <p><strong>Do not add things you have not evaluated personally!</strong><br />
@@ -98,12 +98,12 @@ It's important to add a valid type, this will help us categorize the content and
- <p><strong>Write meaningful commit messages</strong><br >
Meaningful commit messages help speed up the review process as well as help other contributors gain a good overview of the repositories commit history without having to dive into every commit.
Meaningful commit messages help speed up the review process as well as help other contributors in gaining a good overview of the repositories commit history without having to dive into every commit.
</p>
- <p><strong>Look at the existing issues/pull requests before opening new ones</strong></p>
### Good vs. Not So Good Contributions
### Good vs Not So Good Contributions
<strong>Good</strong>
@@ -117,5 +117,5 @@ It's important to add a valid type, this will help us categorize the content and
- Adding whitespace that doesn't add to the readability of the content.
- Rewriting content in a way that doesn't add any value.
- Non-English content.
- PR's that don't follow our style guide, have no description, and a default title.
- PR's that don't follow our style guide, have no description and a default title.
- Links to your own blog articles.

533
package-lock.json generated
View File

@@ -8,8 +8,8 @@
"name": "roadmap.sh",
"version": "1.0.0",
"dependencies": {
"@astrojs/node": "^8.3.3",
"@astrojs/react": "^3.6.2",
"@astrojs/node": "^8.3.2",
"@astrojs/react": "^3.6.1",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"@fingerprintjs/fingerprintjs": "^4.4.3",
@@ -18,7 +18,7 @@
"@resvg/resvg-js": "^2.6.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"astro": "^4.15.4",
"astro": "^4.13.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.12",
"dom-to-image": "^2.6.0",
@@ -132,9 +132,9 @@
}
},
"node_modules/@astrojs/node": {
"version": "8.3.3",
"resolved": "https://registry.npmjs.org/@astrojs/node/-/node-8.3.3.tgz",
"integrity": "sha512-idrKhnnPSi0ABV+PCQsRQqVNwpOvVDF/+fkwcIiE8sr9J8EMvW9g/oyAt8T4X2OBJ8FUzYPL8klfCdG7r0eB5g==",
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/@astrojs/node/-/node-8.3.2.tgz",
"integrity": "sha512-Upv0D+9b3RXp7XViQTtrijaDqihHWbVHLdJQ2sxtPOEtw2GDrVxuC6LmXIUew5YvJ9Ylmpst6KizVwO8d/K9/Q==",
"dependencies": {
"send": "^0.18.0",
"server-destroy": "^1.0.1"
@@ -263,11 +263,11 @@
}
},
"node_modules/@babel/generator": {
"version": "7.25.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
"integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
"dependencies": {
"@babel/types": "^7.25.4",
"@babel/types": "^7.25.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@@ -402,11 +402,11 @@
}
},
"node_modules/@babel/parser": {
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
"integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"dependencies": {
"@babel/types": "^7.25.4"
"@babel/types": "^7.25.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -489,15 +489,15 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.25.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
"integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.4",
"@babel/parser": "^7.25.4",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.4",
"@babel/types": "^7.25.2",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -506,9 +506,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
@@ -2047,36 +2047,10 @@
"node": ">= 10"
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz",
"integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
"cpu": [
"arm"
],
@@ -2086,9 +2060,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz",
"integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
"cpu": [
"arm64"
],
@@ -2098,9 +2072,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz",
"integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
"cpu": [
"arm64"
],
@@ -2110,9 +2084,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz",
"integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
"cpu": [
"x64"
],
@@ -2122,9 +2096,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz",
"integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
"cpu": [
"arm"
],
@@ -2134,9 +2108,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz",
"integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
"cpu": [
"arm"
],
@@ -2146,9 +2120,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz",
"integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
"cpu": [
"arm64"
],
@@ -2158,9 +2132,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz",
"integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
"cpu": [
"arm64"
],
@@ -2170,9 +2144,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz",
"integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
"cpu": [
"ppc64"
],
@@ -2182,9 +2156,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz",
"integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
"cpu": [
"riscv64"
],
@@ -2194,9 +2168,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz",
"integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
"cpu": [
"s390x"
],
@@ -2206,9 +2180,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz",
"integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
"cpu": [
"x64"
],
@@ -2218,9 +2192,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz",
"integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
"cpu": [
"x64"
],
@@ -2230,9 +2204,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz",
"integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
"cpu": [
"arm64"
],
@@ -2242,9 +2216,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz",
"integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
"cpu": [
"ia32"
],
@@ -2254,9 +2228,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz",
"integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
"cpu": [
"x64"
],
@@ -2266,21 +2240,13 @@
]
},
"node_modules/@shikijs/core": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.16.3.tgz",
"integrity": "sha512-yETIvrETCeC39gSPIiSADmjri9FwKmxz0QvONMtTIUYlKZe90CJkvcjPksayC2VQOtzOJonEiULUa8v8crUQvA==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.13.0.tgz",
"integrity": "sha512-Mj5NVfbAXcD1GnwOTSPl8hBn/T8UDpfFQTptp+p41n/CbUcJtOq98WaRD7Lz3hCglYotUTHUWtzu3JhK6XlkAA==",
"dependencies": {
"@shikijs/vscode-textmate": "^9.2.0",
"@types/hast": "^3.0.4",
"oniguruma-to-js": "0.3.3",
"regex": "4.3.2"
"@types/hast": "^3.0.4"
}
},
"node_modules/@shikijs/vscode-textmate": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz",
"integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg=="
},
"node_modules/@shuding/opentype.js": {
"version": "1.4.0-beta.0",
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
@@ -2908,19 +2874,21 @@
}
},
"node_modules/astro": {
"version": "4.15.4",
"resolved": "https://registry.npmjs.org/astro/-/astro-4.15.4.tgz",
"integrity": "sha512-wqy+m3qygt9DmCSqMsckxyK4ccCUFtti2d/WlLkEpAlqHgyDIg20zRTLHO2v/H4YeSlJ8sAcN0RW2FhOeYbINg==",
"version": "4.13.4",
"resolved": "https://registry.npmjs.org/astro/-/astro-4.13.4.tgz",
"integrity": "sha512-uoW961qyU5xxCiUzITVX8wYmdWbSH1zeog9UomoWC5uNpnIbH6WxOPv/qYu2m7W4r2PCxdRqfVXoYjZhFyGfTA==",
"dependencies": {
"@astrojs/compiler": "^2.10.3",
"@astrojs/compiler": "^2.10.2",
"@astrojs/internal-helpers": "0.4.1",
"@astrojs/markdown-remark": "5.2.0",
"@astrojs/telemetry": "3.1.0",
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/types": "^7.25.6",
"@babel/traverse": "^7.25.3",
"@babel/types": "^7.25.2",
"@oslojs/encoding": "^0.4.1",
"@rollup/pluginutils": "^5.1.0",
"@types/babel__core": "^7.20.5",
"@types/cookie": "^0.6.0",
"acorn": "^8.12.1",
@@ -2941,8 +2909,8 @@
"es-module-lexer": "^1.5.4",
"esbuild": "^0.21.5",
"estree-walker": "^3.0.3",
"execa": "^8.0.1",
"fast-glob": "^3.3.2",
"fastq": "^1.17.1",
"flattie": "^1.1.1",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
@@ -2951,11 +2919,8 @@
"js-yaml": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.11",
"magicast": "^0.3.5",
"micromatch": "^4.0.8",
"mrmime": "^2.0.0",
"neotraverse": "^0.6.18",
"ora": "^8.1.0",
"ora": "^8.0.1",
"p-limit": "^6.1.0",
"p-queue": "^8.0.1",
"path-to-regexp": "^6.2.2",
@@ -2963,21 +2928,18 @@
"prompts": "^2.4.2",
"rehype": "^13.0.1",
"semver": "^7.6.3",
"shiki": "^1.16.1",
"shiki": "^1.12.1",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"tinyexec": "^0.3.0",
"tsconfck": "^3.1.3",
"tsconfck": "^3.1.1",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.3",
"vite": "^5.4.2",
"vitefu": "^1.0.2",
"vfile": "^6.0.2",
"vite": "^5.4.0",
"vitefu": "^0.2.5",
"which-pm": "^3.0.0",
"xxhash-wasm": "^1.0.2",
"yargs-parser": "^21.1.1",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.2",
"zod-to-ts": "^1.2.0"
"zod-to-json-schema": "^3.23.2"
},
"bin": {
"astro": "astro.js"
@@ -3360,14 +3322,14 @@
}
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"dependencies": {
"restore-cursor": "^5.0.0"
"restore-cursor": "^4.0.0"
},
"engines": {
"node": ">=18"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4047,6 +4009,28 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -4310,6 +4294,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-tsconfig": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz",
@@ -4694,6 +4689,14 @@
"node": ">= 0.8"
}
},
"node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@@ -4893,6 +4896,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
@@ -5207,16 +5221,6 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/magicast": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
"integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
"dependencies": {
"@babel/parser": "^7.25.4",
"@babel/types": "^7.25.4",
"source-map-js": "^1.2.0"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -5491,6 +5495,11 @@
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -6035,9 +6044,9 @@
]
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -6078,12 +6087,12 @@
"node": ">= 0.6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"engines": {
"node": ">=18"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -6172,14 +6181,6 @@
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/neotraverse": {
"version": "0.6.18",
"resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz",
"integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==",
"engines": {
"node": ">= 10"
}
},
"node_modules/nlcst-to-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz",
@@ -6274,6 +6275,31 @@
"npm": ">=8.12.1"
}
},
"node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npm-run-path/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -6321,12 +6347,18 @@
"wrappy": "1"
}
},
"node_modules/oniguruma-to-js": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.3.3.tgz",
"integrity": "sha512-m90/WEhgs8g4BxG37+Nu3YrMfJDs2YXtYtIllhsEPR+wP3+K4EZk6dDUvy2v2K4MNFDDOYKL4/yqYPXDqyozTQ==",
"node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openai": {
@@ -6356,18 +6388,18 @@
}
},
"node_modules/ora": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-8.1.0.tgz",
"integrity": "sha512-GQEkNkH/GHOhPFXcqZs3IDahXEQcQxsSjEkK4KvEEST4t7eNzoMjxTzef+EZ+JluDEV+Raoi3WQ2CflnRdSVnQ==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz",
"integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==",
"dependencies": {
"chalk": "^5.3.0",
"cli-cursor": "^5.0.0",
"cli-cursor": "^4.0.0",
"cli-spinners": "^2.9.2",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"stdin-discarder": "^0.2.2",
"string-width": "^7.2.0",
"stdin-discarder": "^0.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
@@ -7174,11 +7206,6 @@
"node": ">=8.10.0"
}
},
"node_modules/regex": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz",
"integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw=="
},
"node_modules/rehype": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz",
@@ -7355,34 +7382,47 @@
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=18"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/restore-cursor/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-function": "^5.0.0"
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=18"
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/retext": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz",
@@ -7455,9 +7495,9 @@
"integrity": "sha512-IQejjIfr9RIvesNwp3SyhEq1DMQ2RdJfJhgsb1AyPuKXsfJgOG8F++Cz1p3SIcY0bnB57Q16Ke2VJLjiUVwI3Q=="
},
"node_modules/rollup": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz",
"integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
"dependencies": {
"@types/estree": "1.0.5"
},
@@ -7469,22 +7509,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.1",
"@rollup/rollup-android-arm64": "4.21.1",
"@rollup/rollup-darwin-arm64": "4.21.1",
"@rollup/rollup-darwin-x64": "4.21.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.1",
"@rollup/rollup-linux-arm-musleabihf": "4.21.1",
"@rollup/rollup-linux-arm64-gnu": "4.21.1",
"@rollup/rollup-linux-arm64-musl": "4.21.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.1",
"@rollup/rollup-linux-riscv64-gnu": "4.21.1",
"@rollup/rollup-linux-s390x-gnu": "4.21.1",
"@rollup/rollup-linux-x64-gnu": "4.21.1",
"@rollup/rollup-linux-x64-musl": "4.21.1",
"@rollup/rollup-win32-arm64-msvc": "4.21.1",
"@rollup/rollup-win32-ia32-msvc": "4.21.1",
"@rollup/rollup-win32-x64-msvc": "4.21.1",
"@rollup/rollup-android-arm-eabi": "4.18.1",
"@rollup/rollup-android-arm64": "4.18.1",
"@rollup/rollup-darwin-arm64": "4.18.1",
"@rollup/rollup-darwin-x64": "4.18.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
"@rollup/rollup-linux-arm64-musl": "4.18.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
"@rollup/rollup-linux-x64-gnu": "4.18.1",
"@rollup/rollup-linux-x64-musl": "4.18.1",
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
"@rollup/rollup-win32-x64-msvc": "4.18.1",
"fsevents": "~2.3.2"
}
},
@@ -7715,12 +7755,11 @@
}
},
"node_modules/shiki": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.16.3.tgz",
"integrity": "sha512-GypUE+fEd06FqDs63LSAVlmq7WsahhPQU62cgZxGF+TJT5LjD2k7HTxXj4/CKOVuMM3+wWQ1t4Y5oooeJFRRBQ==",
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-1.13.0.tgz",
"integrity": "sha512-e0dWfnONbEv6xl7FJy3XIhsVHQ/65XHDZl92+6H9+4xWjfdo7pmkqG7Kg47KWtDiEtzM5Z+oEfb4vtRvoZ/X9w==",
"dependencies": {
"@shikijs/core": "1.16.3",
"@shikijs/vscode-textmate": "^9.2.0",
"@shikijs/core": "1.13.0",
"@types/hast": "^3.0.4"
}
},
@@ -7942,6 +7981,17 @@
"node": ">=0.10.0"
}
},
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-outer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
@@ -8163,11 +8213,6 @@
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
},
"node_modules/tinyexec": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
"integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg=="
},
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -8237,9 +8282,9 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
"node_modules/tsconfck": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.3.tgz",
"integrity": "sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz",
"integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==",
"bin": {
"tsconfck": "bin/tsconfck.js"
},
@@ -8710,19 +8755,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
@@ -8932,11 +8964,12 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz",
"integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-stringify-position": "^4.0.0",
"vfile-message": "^4.0.0"
},
"funding": {
@@ -8971,13 +9004,13 @@
}
},
"node_modules/vite": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
"integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz",
"integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.41",
"rollup": "^4.20.0"
"postcss": "^8.4.40",
"rollup": "^4.13.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -9029,9 +9062,9 @@
}
},
"node_modules/vitefu": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.2.tgz",
"integrity": "sha512-0/iAvbXyM3RiPPJ4lyD4w6Mjgtf4ejTK6TPvTNG3H32PLwuT0N/ZjJLiXug7ETE/LWtTeHw9WRv7uX/tIKYyKg==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
"integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
"peerDependencies": {
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
},
@@ -9272,11 +9305,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/xxhash-wasm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz",
"integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A=="
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -9333,15 +9361,6 @@
"zod": "^3.23.3"
}
},
"node_modules/zod-to-ts": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz",
"integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==",
"peerDependencies": {
"typescript": "^4.9.4 || ^5.0.2",
"zod": "^3"
}
},
"node_modules/zustand": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz",

View File

@@ -29,8 +29,8 @@
"test:e2e": "playwright test"
},
"dependencies": {
"@astrojs/node": "^8.3.3",
"@astrojs/react": "^3.6.2",
"@astrojs/node": "^8.3.2",
"@astrojs/react": "^3.6.1",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"@fingerprintjs/fingerprintjs": "^4.4.3",
@@ -39,7 +39,7 @@
"@resvg/resvg-js": "^2.6.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"astro": "^4.15.4",
"astro": "^4.13.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.12",
"dom-to-image": "^2.6.0",

872
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -184,52 +184,23 @@
},
"Dp2DOX10u2xJUjB8Okhzh": {
"title": "Frame",
"description": "**FrameLayout** is a simple ViewGroup subclass in Android that is designed to hold a single child view or a stack of overlapping child views. It positions each child in the top-left corner by default and allows them to overlap on top of each other, which makes it useful for situations where you need to layer views on top of one another.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: Frame Layout",
"url": "https://developer.android.com/reference/android/widget/FrameLayout",
"type": "article"
}
]
"description": "",
"links": []
},
"U8iMGGOd2EgPxSuwSG39Z": {
"title": "Linear",
"description": "**LinearLayout** is a view group that aligns all children in a single directioni, vertically or horizontally. You can specify the layout direction with the `android:orientation` attribute.\n\n**LinearLayout** was commonly used in earlier Android development, but with the introduction of ConstraintLayout, its less frequently used in modern apps.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: Linear Layout",
"url": "https://developer.android.com/develop/ui/views/layout/linear",
"type": "article"
}
]
"description": "",
"links": []
},
"yE0qAQZiEC9R8WvCdskpr": {
"title": "Relative",
"description": "A **RelativeLayout** in Android is a type of ViewGroup that allows you to position child views relative to each other or relative to the parent layout. It's a flexible layout where you can arrange the child views in relation to one another based on certain rules, making it suitable for creating complex UI designs.\n\n**RelativeLayout** was commonly used in earlier Android development, but with the introduction of `ConstraintLayout`, it's less frequently used in modern apps.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: Relative Layout",
"url": "https://developer.android.com/develop/ui/views/layout/relative",
"type": "article"
}
]
"description": "",
"links": []
},
"3fFNMhQIuuh-NRzSXYpXO": {
"title": "Constraint",
"description": "Lets you create large, complex layouts with a flat view hierarchy—no nested view groups. It's similar to `RelativeLayout` in that all views are laid out according to relationships between sibling views and the parent layout, but it's more flexible than RelativeLayout and easier to use. Its available on xml and jetpack compose.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: ConstraintLayout in xml",
"url": "https://developer.android.com/develop/ui/views/layout/constraint-layout",
"type": "article"
},
{
"title": "Android developers: ContraintLayout in compose",
"url": "https://developer.android.com/develop/ui/compose/layouts/constraintlayout",
"type": "article"
}
]
"description": "",
"links": []
},
"xIvplWfe-uDr9iHjPT1Mx": {
"title": "RecycleView",
@@ -332,30 +303,13 @@
},
"EzLjX4iRT7AxkAOsJYnSU": {
"title": "ListView",
"description": "Displays a vertically-scrollable collection of views, where each view is positioned immediatelybelow the previous view in the list.\n\nFor a more modern, flexible, and performant approach to displaying lists, use `RecyclerView`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: ListView",
"url": "https://developer.android.com/reference/android/widget/ListView",
"type": "article"
}
]
"description": "",
"links": []
},
"amTxz7mS98lkhOrNMJXG_": {
"title": "Drawer",
"description": "The **Navigation Drawer** in Android is a sliding menu from the left that simplifies navigation between important app links. It opens by sliding or via an icon in the `ActionBar`. Its an overlay panel that replaces a screen dedicated to displaying options.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Android developers: DrawerLayout",
"url": "https://developer.android.com/reference/androidx/drawerlayout/widget/DrawerLayout",
"type": "article"
},
{
"title": "Navigate Drawer Tutorial",
"url": "https://www.digitalocean.com/community/tutorials/android-navigation-drawer-example-tutorial",
"type": "article"
}
]
"description": "",
"links": []
},
"pEBpXv3Jf1AzBNHlvVrG8": {
"title": "Tabs",
@@ -370,19 +324,8 @@
},
"Xn1VQ-xOT67ZfJJTM4r1p": {
"title": "Animations",
"description": "`Animations` can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Google developers: Animations",
"url": "https://developer.android.com/develop/ui/views/animations/overview",
"type": "article"
},
{
"title": "Google developers: Animations",
"url": "https://www.youtube.com/watch?v=N_x7SV3I3P0",
"type": "video"
}
]
"description": "",
"links": []
},
"60Vm-77rseUqpMiFvp-dA": {
"title": "Jetpack Compose",
@@ -412,14 +355,8 @@
},
"Bz-BkfzsDHAbAw3HD7WCd": {
"title": "MVI",
"description": "The **MVI** `Model-View-Intent` pattern is a reactive architectural pattern, similar to **MVVM** and **MVP**, focusing on immutability and handling states in unidirectional cycles. The data flow is unidirectional: Intents update the Model's state through the `ViewModel`, and then the View reacts to the new state. This ensures a clear and predictable cycle between logic and the interface.\n\n* Model: Represents the UI state. It is immutable and contains all the necessary information to represent a screen.\n* View: Displays the UI state and receives the user's intentions.\n* Intent: The user's intentions trigger state updates, managed by the `ViewModel`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "MVI with Kotlin",
"url": "https://proandroiddev.com/mvi-architecture-with-kotlin-flows-and-channels-d36820b2028d",
"type": "article"
}
]
"description": "",
"links": []
},
"pSU-NZtjBh-u0WKTYfjk_": {
"title": "MVVM",

View File

@@ -247,34 +247,8 @@
},
"19c7D-fWIJ3vYFT6h8ZfN": {
"title": "Communication",
"description": "Angular components can communicate with each other using `@Input()` and `@Output()` decorators. These decorators facilitate data exchange between parent and child components.\n\n* **@Input()**: This decorator allows a parent component to pass data to a child component, enabling the child to receive and use the data.\n* **@Output()**: This decorator allows a child component to emit events to a parent component, enabling the parent to respond to changes or actions within the child component.\n\nAdditionally, **model inputs** are a special type of input that enable two-way data binding. This means that changes in the child component can be propagated back to the parent component, ensuring synchronization between the two. Model inputs automatically create a corresponding output, typically named by appending “Change” to the inputs name, to facilitate this two-way communication.\n\nTo facilitate communication between unrelated components, its most effective to trigger events using `EventEmitter` and have the components listen for these events. This approach ensures a decoupled and flexible architecture, allowing components to interact seamlessly without direct dependencies.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Website - Inputs",
"url": "https://angular.dev/guide/components/inputs",
"type": "article"
},
{
"title": "Angular Official Website - Outputs",
"url": "https://angular.dev/guide/components/outputs",
"type": "article"
},
{
"title": "Angular Official Docs - Model Inputs",
"url": "https://angular.dev/guide/signals/model",
"type": "article"
},
{
"title": "Custom events with outputs",
"url": "https://angular.dev/guide/components/outputs",
"type": "article"
},
{
"title": "Non-Related Component Communication | Angular Component & Directives",
"url": "https://www.youtube.com/watch?v=aIkGXMJFTzM",
"type": "video"
}
]
"description": "",
"links": []
},
"TDyFjKrIZJnCjEZsojPNQ": {
"title": "Parent-Child Interaction",
@@ -357,34 +331,8 @@
},
"tC5ETtOuuUcybj1jI4CuG": {
"title": "Dynamic Components",
"description": "In addition to using a component directly in a template, you can also dynamically render components. There are two main ways to dynamically render a component: in a template with `NgComponentOutlet`, or in your TypeScript code with `ViewContainerRef`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Programmatically rendering components",
"url": "https://angular.dev/guide/components/programmatic-rendering",
"type": "article"
},
{
"title": "New Input Binding for NgComponentOutlet",
"url": "https://medium.com/ngconf/new-input-binding-for-ngcomponentoutlet-cb18a86a739d",
"type": "article"
},
{
"title": "Render dynamic components in Angular using ViewContainerRef",
"url": "https://dev.to/railsstudent/render-dynamic-components-in-angular-using-viewcontainerref-160h",
"type": "article"
},
{
"title": "Dynamic Component in Angular (2024)",
"url": "https://www.youtube.com/watch?v=ncbftt3NWVo",
"type": "video"
},
{
"title": "Mastering ViewContainerRef for dynamic component loading in Angular17",
"url": "https://www.youtube.com/watch?v=Ra4PITCt8m0",
"type": "video"
}
]
"description": "",
"links": []
},
"b_kdNS9PDupcUftslkf9i": {
"title": "Modules",
@@ -658,6 +606,11 @@
}
]
},
"Wc2ybRw43uamEtno0FpDv": {
"title": "Template Ref Vars",
"description": "",
"links": []
},
"VsU6713jeIjAOEZnF6gWx": {
"title": "@Input & @Output",
"description": "`@Input()` and `@Output()` give a child component a way to communicate with its parent component. `@Input()` lets a parent component update data in the child component. Conversely, `@Output()` lets the child send data to a parent component.\n\nVisit the following resources to learn more:",
@@ -687,24 +640,8 @@
},
"VsC7UmE_AumsBP8fC6to1": {
"title": "Template Syntax",
"description": "In Angular, a _template_ is a chunk of HTML. Use special syntax within a template to build on many of Angular's features. Extend the HTML vocabulary of your applications with special Angular syntax in your templates. For example, Angular helps you get and set DOM (Document Object Model) values dynamically with features such as built-in template functions, variables, event listening, and data binding.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Template Syntax",
"url": "https://angular.dev/guide/templates",
"type": "article"
},
{
"title": "An Introduction to Angular Template Syntax",
"url": "https://angularstart.com/modules/basic-angular-concepts/3/",
"type": "article"
},
{
"title": "Craft Dynamic Templates with Angular's Template Syntax",
"url": "https://www.youtube.com/watch?v=uSnUTcf8adI",
"type": "video"
}
]
"description": "",
"links": []
},
"U1Zy2T-2ki9pDkXn9hn-I": {
"title": "@if",
@@ -740,19 +677,8 @@
},
"ys5untkSppGMFK-VsfuRt": {
"title": "@else if",
"description": "With the new control flow syntax, you gain `@else if` conditional blocks, something that is not possible with `@ngIf`. This addition makes the control flow syntax close to what we would write with just plain JavaScript.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - @if",
"url": "https://angular.dev/api/core/@if",
"type": "article"
},
{
"title": "Angular @if: Complete Guide",
"url": "https://blog.angular-university.io/angular-if/",
"type": "article"
}
]
"description": "",
"links": []
},
"2kYS9w1UzQFZ1zhf01m9L": {
"title": "@for",
@@ -767,51 +693,18 @@
},
"nZuim4Fjq6jYOXcRTAEay": {
"title": "@switch",
"description": "The `@switch` blocks displays content selected by one of the cases matching against the conditional expression. The value of the conditional expression is compared to the case expression using the `===` operator. `@switch` does not have fallthrough, so you do not need an equivalent to a break or return statement.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - @switch",
"url": "https://angular.dev/guide/templates/control-flow#switch-block---selection",
"type": "article"
},
{
"title": "Angular @switch: Complete Guide",
"url": "https://blog.angular-university.io/angular-switch/",
"type": "article"
}
]
"description": "",
"links": []
},
"cHC2MH50CbUSMRZV4QGJI": {
"title": "@case",
"description": "If no `@case` matches the `@switch` condition and there is no `@default` block, nothing is shown. Otherwise, the content inside the `@case` that matches the condition will be displayed.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - @switch",
"url": "https://angular.dev/guide/templates/control-flow#switch-block---selection",
"type": "article"
},
{
"title": "Angular @switch: Complete Guide",
"url": "https://blog.angular-university.io/angular-switch/",
"type": "article"
}
]
"description": "",
"links": []
},
"h4MMn0_qUN3YXEdMUJOyd": {
"title": "@default",
"description": "The `@default` clause is used to render a template when none of the `@case` blocks matches the value of the `@switch` conditional. `@default` is optional and can be omitted.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - @switch",
"url": "https://angular.dev/guide/templates/control-flow#switch-block---selection",
"type": "article"
},
{
"title": "Angular @switch: Complete Guide",
"url": "https://blog.angular-university.io/angular-switch/",
"type": "article"
}
]
"description": "",
"links": []
},
"AwOM0ucg6W7TohdUd7KWT": {
"title": "@let",
@@ -856,7 +749,7 @@
"links": [
{
"title": "Understanding Pipes",
"url": "https://angular.dev/tutorials/learn-angular/22-pipes",
"url": "https://angular.dev/guide/pipes",
"type": "article"
},
{
@@ -1081,7 +974,7 @@
},
"YF_sG292HqawIX0siWhrv": {
"title": "Router Events",
"description": "The Angular Router raises events when it navigates from one route to another route. It raises several events such as `NavigationStart`, `NavigationEnd`, `NavigationCancel`, `NavigationError`, `ResolveStart`, etc. You can listen to these events and find out when the state of the route changes. Some of the useful events are route change start (NavigationStart) and route change end (NavigationEnd).\n\nVisit the following resources to learn more:",
"description": "The Angular Router raises events when it navigates from one route to another route. It raises several events such as `NavigationStart`, `NavigationEnd`, `NavigationCancel`, `NavigationError`, `ResolveStart`, etc. You can listen to these events and find out when the state of the route changes. Some of the useful events are route change start (NavigationStart) and route change end (NavigationEnd).",
"links": [
{
"title": "Router reference - Router events",
@@ -1092,11 +985,6 @@
"title": "Router event - API",
"url": "https://angular.dev/api/router/RouterEvent",
"type": "article"
},
{
"title": "Router events in Angular",
"url": "https://medium.com/@gurunadhpukkalla/router-events-in-angular-3112a3968660",
"type": "article"
}
]
},
@@ -1217,34 +1105,8 @@
},
"XC_K1Wahl2ySqOXoym4YU": {
"title": "Typed Forms",
"description": "Since Angular 14, reactive forms are strictly typed by default. You don't have to define extra custom types or add a ton of type annotations to your form declarations to benefit from this extra type safety, as Angular will infer types from the default value of a form control. Non-typed forms are still supported. To use them, you must import the `Untyped` symbols from `@angular/forms`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - @let",
"url": "https://angular.dev/api/core/@let",
"type": "article"
},
{
"title": "Angular Strictly Typed Forms (Complete Guide)",
"url": "https://blog.angular-university.io/angular-typed-forms/",
"type": "article"
},
{
"title": "Getting started with typed reactive forms in Angular",
"url": "https://www.youtube.com/watch?v=mT3UR0TdDnU",
"type": "video"
},
{
"title": "Angular TYPED Forms: Are You Using Them Correctly?",
"url": "https://www.youtube.com/watch?v=it2BZoIvBPc",
"type": "video"
},
{
"title": "Knowing this makes Angular typed forms WAY less awkward",
"url": "https://www.youtube.com/watch?v=xpRlijg6spo",
"type": "video"
}
]
"description": "",
"links": []
},
"uDx4lPavwsJFBMzdQ70CS": {
"title": "Template-driven Forms",
@@ -1401,29 +1263,8 @@
},
"lfp7PIjwITU5gBITQdirD": {
"title": "RxJS Basics",
"description": "Reactive Extensions for JavaScript, or RxJS, is a reactive library used to implement reactive programming to deal with async implementation, callbacks, and event-based programs.\n\nThe reactive paradigm can be used in many different languages through the use of reactive libraries. These libraries are downloaded APIs that provide functionalities for reactive tools like observers and operators. It can be used in your browser or with Node.js.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "RxJS Docs",
"url": "https://rxjs.dev/guide/overview",
"type": "article"
},
{
"title": "Learn RxJS",
"url": "https://www.learnrxjs.io/",
"type": "article"
},
{
"title": "RxJs and Observables for Beginners: A Beginner Friendly Introduction",
"url": "https://blog.angular-university.io/functional-reactive-programming-for-angular-2-developers-rxjs-and-observables/",
"type": "article"
},
{
"title": "Beginner's RxJS Tutorial: Dive Deep with RxJS Crash Course!",
"url": "https://www.youtube.com/watch?v=yJdh1_FbtjU",
"type": "video"
}
]
"description": "Reactive Extensions for JavaScript, or RxJS, is a reactive library used to implement reactive programming to deal with async implementation, callbacks, and event-based programs.\n\nThe reactive paradigm can be used in many different languages through the use of reactive libraries. These libraries are downloaded APIs that provide functionalities for reactive tools like observers and operators. It can be used in your browser or with Node.js.",
"links": []
},
"krXA6ua7E3m4IIpFkgQZe": {
"title": "Observable Pattern",
@@ -1518,13 +1359,8 @@
},
"kdMJHljMzGA3oRlh8Zvos": {
"title": "Transformation",
"description": "In RxJS, \"transformation\" refers to the process of modifying or manipulating the data emitted by an Observable. There are a variety of methods available in RxJS that can be used to transform the data emitted by an Observable, including:\n\n* **map**: applies a function to each item emitted by the Observable and emits the resulting value\n* **mergeMap**: applies a function to each item emitted by the Observable, and then merges the resulting Observables into a single Observable\n* **switchMap**: applies a function to each item emitted by the Observable, and then switches to the latest resulting Observable\n* **concatMap**: applies a function to each item emitted by the Observable, and then concatenates the resulting Observables into a single Observable\n* **exhaustMap**: applies a function to each item emitted by the Observable, but ignores subsequent emissions until the current Observable completes\n\nThese are just a few examples of the many methods available in RxJS for transforming the data emitted by an Observable. Each method has its own specific use case, and the best method to use will depend on the requirements of your application.\n\nVisit the following resources to learn more:",
"description": "In RxJS, \"transformation\" refers to the process of modifying or manipulating the data emitted by an Observable. There are a variety of methods available in RxJS that can be used to transform the data emitted by an Observable, including:\n\n* map: applies a function to each item emitted by the Observable and emits the resulting value\n* mergeMap: applies a function to each item emitted by the Observable, and then merges the resulting Observables into a single Observable\n* switchMap: applies a function to each item emitted by the Observable, and then switches to the latest resulting Observable\n* concatMap: applies a function to each item emitted by the Observable, and then concatenates the resulting Observables into a single Observable\n* exhaustMap: applies a function to each item emitted by the Observable, but ignores subsequent emissions until the current Observable completes\n\nThese are just a few examples of the many methods available in RxJS for transforming the data emitted by an Observable. Each method has its own specific use case, and the best method to use will depend on the requirements of your application.\n\nHere are the official documentation links for the RxJS transformation methods:\n\nYou can find more information and examples on these methods in the official RxJS documentation. Additionally, you can find more operators on [https://rxjs.dev/api/operators](https://rxjs.dev/api/operators) and you can also find more information on the library as a whole on [https://rxjs.dev/](https://rxjs.dev/)",
"links": [
{
"title": "RxJs Docs - Operators",
"url": "https://rxjs.dev/api/operators",
"type": "article"
},
{
"title": "map",
"url": "https://rxjs.dev/api/operators/map",
@@ -1783,7 +1619,7 @@
},
"m4WBnx_9h01Jl6Q1sxi4Y": {
"title": "Zones",
"description": "Zone.js is a signaling mechanism that Angular uses to detect when an application state might have changed. It captures asynchronous operations like setTimeout, network requests, and event listeners. Angular schedules change detection based on signals from Zone.js.\n\nVisit the following resources to learn more:",
"description": "Zone.js is a signaling mechanism that Angular uses to detect when an application state might have changed. It captures asynchronous operations like setTimeout, network requests, and event listeners. Angular schedules change detection based on signals from Zone.js.",
"links": [
{
"title": "Resolving zone pollution",
@@ -1799,11 +1635,6 @@
"title": "NgZone - API",
"url": "https://angular.dev/api/core/NgZone",
"type": "article"
},
{
"title": "WTF is \"Zone.js\" and is it making your app slow?",
"url": "https://www.youtube.com/watch?v=lmrf_gPIOZU",
"type": "video"
}
]
},
@@ -1845,29 +1676,8 @@
},
"EbJib-XfZFF9bpCtL3aBs": {
"title": "Developer Tools",
"description": "Angular offers a suite of powerful developer tools designed to streamline and enhance the development process. These include the Angular CLI for efficient project setup and management, the Angular DevTools extension for advanced debugging and profiling, and the Angular Language Service for improved code editing and completion. Leveraging these tools will significantly improve your ability to write high-quality Angular code.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "VS Code NG Language Service",
"url": "https://github.com/angular/vscode-ng-language-service",
"type": "opensource"
},
{
"title": "Angular Official Docs - devtools",
"url": "https://angular.dev/tools/devtools",
"type": "article"
},
{
"title": "Angular Official Docs - CLI",
"url": "https://angular.dev/tools/cli",
"type": "article"
},
{
"title": "Language Service Docs",
"url": "https://angular.dev/tools/language-service",
"type": "article"
}
]
"description": "",
"links": []
},
"4YSk6I63Ew--zoXC3xmrC": {
"title": "Angular CLI",
@@ -1897,106 +1707,18 @@
},
"FVH0lnbIZ2m5EfF2EJ2DW": {
"title": "Local Setup",
"description": "To install Angular CLI on your local system, you need to install `Node.js`. Angular requires an active LTS or maintenance LTS version of Node. Angular CLI uses Node and its associated package manager, npm, to install and run JavaScript tools outside the browser. Once you have Node installed, you can run `npm install -g @angular/cli` to install the Angular CLI.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Local set-up",
"url": "https://angular.dev/tools/cli/setup-local",
"type": "article"
},
{
"title": "Angular Official Docs - Version compatibility guide",
"url": "https://angular.dev/reference/versions",
"type": "article"
},
{
"title": "How To Install Angular CLI In Windows 10 | In Under 2 Minutes!",
"url": "https://www.youtube.com/watch?v=vjgACKkPENg",
"type": "video"
},
{
"title": "How to Install Multiple Versions of Angular in Your Development Environment",
"url": "https://www.youtube.com/watch?v=LYNG3kcKRQ8",
"type": "video"
}
]
"description": "",
"links": []
},
"1fVi9AK6aLjt5QgAFbnGX": {
"title": "Deployment",
"description": "The Angular CLI command `ng deploy` executes the deploy CLI builder associated with your project. A number of third-party builders implement deployment capabilities to different platforms. You can add any of them to your project with `ng add`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "NGX AWS Deploy",
"url": "https://github.com/Jefiozie/ngx-aws-deploy",
"type": "opensource"
},
{
"title": "Angular CLI GitHub Pages",
"url": "https://github.com/angular-schule/angular-cli-ghpages",
"type": "opensource"
},
{
"title": "Angular Official Docs - Deployment",
"url": "https://angular.dev/tools/cli/deployment",
"type": "article"
},
{
"title": "Firebase Hosting",
"url": "https://firebase.google.com/docs/hosting",
"type": "article"
},
{
"title": "Vercel: Angular Solutions",
"url": "https://vercel.com/solutions/angular",
"type": "article"
},
{
"title": "Netlify",
"url": "https://docs.netlify.com/frameworks/angular/",
"type": "article"
},
{
"title": "Cloudflare Pages",
"url": "https://developers.cloudflare.com/pages/framework-guides/deploy-an-angular-site/#create-a-new-project-using-the-create-cloudflare-cli-c3",
"type": "article"
},
{
"title": "AWS Amplify",
"url": "https://docs.amplify.aws/angular/",
"type": "article"
}
]
"description": "",
"links": []
},
"yhNGhduk__ow8VTLc6inZ": {
"title": "End-to-End Testing",
"description": "End-to-end or (E2E) testing is a form of testing used to assert your entire application works as expected from start to finish or \"end-to-end\". E2E testing differs from unit testing in that it is completely decoupled from the underlying implementation details of your code. It is typically used to validate an application in a way that mimics the way a user would interact with it. The `ng e2e` command will first check your project for the \"e2e\" target. If it can't locate it, the CLI will then prompt you which e2e package you would like to use and walk you through the setup.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - End to End Testing",
"url": "https://angular.dev/tools/cli/end-to-end",
"type": "article"
},
{
"title": "Cypress Official Docs - Your First Test with Cypress",
"url": "https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test",
"type": "article"
},
{
"title": "Nightwatch Official Docs - Writing Tests: Introduction",
"url": "https://nightwatchjs.org/guide/writing-tests/introduction.html",
"type": "article"
},
{
"title": "Webdriver Official Docs - Getting Started",
"url": "https://webdriver.io/docs/gettingstarted/",
"type": "article"
},
{
"title": "Puppeteer Angular Schematic",
"url": "https://pptr.dev/guides/ng-schematics/#getting-started",
"type": "article"
}
]
"description": "",
"links": []
},
"Uvr0pRk_fOzwRwqn0dQ6N": {
"title": "Schematics",
@@ -2016,40 +1738,13 @@
},
"Ax-s_xw3FO3Ocv-AnLbQD": {
"title": "Build Environments",
"description": "You can define different named build configurations for your project, such as `development` and `production`, with different defaults. Each named configuration can have defaults for any of the options that apply to the various builder targets, such as `build`, `serve`, and `test`. The Angular CLI can replace files for each environment if you pass a `--configuration` flag with the named configuration when running a CLI command.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Build environments",
"url": "https://angular.dev/tools/cli/environments#using-environment-specific-variables-in-your-app",
"type": "article"
},
{
"title": "Building an Angular application in various environments using Angular CLI and server",
"url": "https://medium.com/yavar/building-an-angular-application-in-various-environments-using-angular-cli-and-server-18f94067154b",
"type": "article"
}
]
"description": "",
"links": []
},
"TeWEy9I-hU6SH02Sy2S2S": {
"title": "CLI Builders",
"description": "A number of Angular CLI commands run a complex process on your code, such as building, testing, or serving your application. The commands use an internal tool called `Architect` to run CLI builders, which invoke another tool (bundler, test runner, server) to accomplish the desired task. Custom builders can perform an entirely new task or to change which third-party tool is used by an existing command.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Builders",
"url": "https://github.com/just-jeb/angular-builders",
"type": "opensource"
},
{
"title": "Angular Official Docs - CLI Builders",
"url": "https://angular.dev/tools/cli/cli-builder",
"type": "article"
},
{
"title": "Angular Builders Creating Custom Builder from Scratch",
"url": "https://www.youtube.com/watch?v=QbDkDLnXAZE",
"type": "video"
}
]
"description": "",
"links": []
},
"MwtM1UAIfj4FJ-Y4CKDsP": {
"title": "AoT Compilation",
@@ -2127,40 +1822,13 @@
},
"YHV5oFwLwphXf1wJTDZuG": {
"title": "Using Libraries",
"description": "Libraries are published as `npm packages`, usually together with schematics that integrate them with the Angular CLI. To integrate reusable library code into an application, you need to install the package and import the provided functionality in the location you use it. For most published Angular libraries, use the `ng add <lib_name>` Angular CLI command. A published library typically provides a `README` file or other documentation on how to add that library to your application. A library is able to be updated by the publisher, and also has individual dependencies which need to be kept current. To check for updates to your installed libraries, use the `ng update` Angular CLI command.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Using Libraries",
"url": "https://angular.dev/tools/libraries/using-libraries",
"type": "article"
},
{
"title": "npm",
"url": "https://www.npmjs.com/",
"type": "article"
}
]
"description": "",
"links": []
},
"A1mYMg7cbcj6p_VkDf-Tz": {
"title": "Creating Libraries",
"description": "If you have developed features that are suitable for reuse, you can create your own libraries. These libraries can be used locally in your workspace, or you can publish them as npm packages to share with other projects or other Angular developers. Putting code into a separate library is more complex than simply putting everything in one application. It requires more of an investment in time and thought for managing, maintaining, and updating the library. This complexity can pay off when the library is being used in multiple applications.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "NG Packagr",
"url": "https://github.com/ng-packagr/ng-packagr",
"type": "opensource"
},
{
"title": "Angular Official Docs - Creating Libraries",
"url": "https://angular.dev/tools/libraries/creating-libraries",
"type": "article"
},
{
"title": "Angular Official Docs - File Structure: Library project files",
"url": "https://angular.dev/reference/configs/file-structure#library-project-files",
"type": "article"
}
]
"description": "",
"links": []
},
"jfHaS8TqE4tcAo59K8Nkn": {
"title": "SSR",
@@ -2185,24 +1853,8 @@
},
"b-0yQ74zHtAxI9aRLBohc": {
"title": "SSG",
"description": "SSG (Static Site Generator) helps in building the HTML full website during the process of building and serving that HTML page. This method helps to generate the HTML website on the client side before it's served on the server side. Therefore, whenever a user requests a HTML page, the HTML page will be rendered, and secondly, the Angular app will be rendered. The SSG can be used only if your website is static or its content doesn't change frequently.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Air with Alyssa Nicoll - SSR, SSG, ISR, & SOS",
"url": "https://www.youtube.com/watch?v=b0pUU7RJbBQ",
"type": "podcast"
},
{
"title": "Angular Official Docs - Prerendering (SSG)",
"url": "https://angular.dev/guide/prerendering",
"type": "article"
},
{
"title": "Angular 16 Pre Rendering Static Pages - Static Site Generation SSG",
"url": "https://www.youtube.com/watch?v=vmOWJvm3apA",
"type": "video"
}
]
"description": "SSG (Static Site Generator), helps in building the HTML full website, during the process of building and serving that HTML Page. This method helps to generate the HTML website on the client side before its being served on the server side. Therefore, whenever a user requests a HTML Page, firstly HTML page will be rendered and secondly, the angular app will be rendered. The SSG can be used only if your website is static (or) it's content doesn't changes frequently.",
"links": []
},
"kauQofxCmpktXPcnzid17": {
"title": "AnalogJS",
@@ -2312,66 +1964,18 @@
},
"xH3RHPhsaqD9zIMms5OmX": {
"title": "HTTP Vulnerabilities",
"description": "Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily on the server side, but Angular provides helpers to make integration on the client side easier.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Security",
"url": "https://angular.dev/best-practices/security",
"type": "article"
},
{
"title": "Angular | HackTricks",
"url": "https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/angular",
"type": "article"
}
]
"description": "",
"links": []
},
"Z1DZBbFI4oU6-KQg3wqMm": {
"title": "Cross-site Request Forgery",
"description": "Cross-site request forgery, also known as one-click attack or session riding and abbreviated as CSRF or XSRF, is a type of malicious exploit of a website or web application where unauthorized commands are submitted from a user that the web application trusts. There are many ways in which a malicious website can transmit such commands; specially-crafted image tags, hidden forms, and JavaScript fetch or XMLHttpRequests, for example, can all work without the user's interaction or knowledge. Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user's browser. In a CSRF attack, an innocent end user is tricked by an attacker into submitting a web request that they did not intend. This may cause actions to be performed on the website that can include inadvertent client or server data leakage, change of session state, or manipulation of an end user's account. Angular provides built-in protection against CSRF attacks through the `HttpClientXsrfModule` module. This module automatically adds a token to outgoing requests and validates it on the server side.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Cross Site Request Forgery",
"url": "https://angular.dev/best-practices/security#cross-site-request-forgery",
"type": "article"
},
{
"title": "HttpClientXsrfModule",
"url": "https://angular.dev/api/common/http/HttpClientXsrfModule",
"type": "article"
},
{
"title": "Configure the CSRF Protection With Spring Security 6 and Angular",
"url": "https://www.youtube.com/watch?v=tgjLsEmxcuY",
"type": "video"
},
{
"title": "Angular security - CSRF prevention using Double Submit Cookie",
"url": "https://www.youtube.com/watch?v=lZfF4MOTeNM",
"type": "video"
}
]
"description": "",
"links": []
},
"m2aw8vb4rz4IjshpoMyNx": {
"title": "HttpClient CSRF",
"description": "HttpClient includes a built-in mechanism to prevent XSRF attacks. When making HTTP requests, an interceptor reads a token from a cookie (default name: XSRF-TOKEN) and sets it as an HTTP header (X-XSRF-TOKEN). Since only code running on your domain can read this cookie, the backend can verify that the HTTP request originates from your client application and not from an attacker.\n\nHowever, HttpClient only handles the client-side aspect of XSRF protection. Your backend service must be configured to set the cookie for your page and verify that the header is present on all relevant requests. Without this backend configuration, Angulars default XSRF protection will not be effective.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Security",
"url": "https://angular.dev/best-practices/security#httpclient-xsrf-csrf-security",
"type": "article"
},
{
"title": "How can you protect Angular Web app from cross site request forgery?",
"url": "https://www.linkedin.com/advice/3/how-can-you-protect-angular-web-app-from-cross-site-pyqwc",
"type": "article"
},
{
"title": "Cross Site Request Forgery: XSRF protection in Angular",
"url": "https://borstch.com/blog/development/cross-site-request-forgery-xsrf-protection-in-angular",
"type": "article"
}
]
"description": "",
"links": []
},
"ni00edsphJd7uBLCn7-Vw": {
"title": "XSRF protection",
@@ -2380,24 +1984,8 @@
},
"zd7YJGlcMFNFbsKUiW_XC": {
"title": "Cross-site Script Inclusion",
"description": "Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to read data from a JSON API. The attack works on older browsers by overriding built-in JavaScript object constructors, and then including an API URL using a `<script>` tag. Angular's HttpClient library recognizes this convention and automatically strips the string \")\\]}',\\\\n\" from all responses before further parsing.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Cross Site Script Inclusion",
"url": "https://angular.dev/best-practices/security#cross-site-script-inclusion-xssi",
"type": "article"
},
{
"title": "XSSI Cross Site Script Inclusion",
"url": "https://book.hacktricks.xyz/pentesting-web/xssi-cross-site-script-inclusion",
"type": "article"
},
{
"title": "Testing for Cross Site Script Inclusion",
"url": "https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/11-Client_Side_Testing/13-Testing_for_Cross_Site_Script_Inclusion",
"type": "article"
}
]
"description": "",
"links": []
},
"VNG9DdXlS6R1OJ6Lrn4Lt": {
"title": "Accessibility",
@@ -2565,118 +2153,28 @@
},
"pRSR5PEbkJXAJ1LPyK-EE": {
"title": "Zone Pollution",
"description": "`Zone.js` is a signaling mechanism that Angular uses to detect when an application state might have changed. In some cases, scheduled tasks or microtasks dont make any changes in the data model, which makes running change detection unnecessary. Common examples are `requestAnimationFrame`, `setTimeout` and `setInterval`. You can identify change detection with Angular DevTools, and you can run code outside the Angular zone to avoid unnecessary change detection calls.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Zone Pollution",
"url": "https://angular.dev/best-practices/zone-pollution",
"type": "article"
},
{
"title": "Angular Official Docs - DevTools",
"url": "https://angular.dev/tools/devtools",
"type": "article"
},
{
"title": "NgZone in Angular - Improve Performance by Running Code Outside Angular",
"url": "https://www.youtube.com/watch?v=7duYY9IFIuw",
"type": "video"
},
{
"title": "4 Runtime Performance Optimizations",
"url": "https://www.youtube.com/watch?v=f8sA-i6gkGQ",
"type": "video"
}
]
"description": "",
"links": []
},
"yxUtSBzJPRcS-IuPsyp-W": {
"title": "Slow Computations",
"description": "On every change detection cycle, Angular synchronously evaluates all template expressions in components based on their detection strategy and executes the `ngDoCheck`, `ngAfterContentChecked`, `ngAfterViewChecked`, and `ngOnChanges` lifecycle hooks. To remove slow computations, you can optimize algorithms, cache data with pure pipes or memoization, and limit lifecycle hook usage.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Slow Computations",
"url": "https://angular.dev/best-practices/slow-computations",
"type": "article"
},
{
"title": "Angular Performance Optimization",
"url": "https://davembush.medium.com/angular-performance-optimization-5ec630d2b8f1",
"type": "article"
}
]
"description": "",
"links": []
},
"NY_MfBNgNmloiRGcIvfJ1": {
"title": "Hydration",
"description": "Hydration is the process that restores the server-side rendered application on the client. This includes things like reusing the server rendered DOM structures, persisting the application state, transferring application data that was retrieved already by the server, and other processes. Hydration can be enabled for server-side rendered (SSR) applications only. You can enable hydration manually by visiting your main application component or module and importing `provideClientHydration` from `@angular/platform-browser`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Hydration",
"url": "https://angular.dev/guide/hydration",
"type": "article"
},
{
"title": "Angular Official Docs - provideClientHydration",
"url": "https://angular.dev/api/platform-browser/provideClientHydration",
"type": "article"
},
{
"title": "Angular Hydration",
"url": "https://www.bacancytechnology.com/blog/angular-hydration",
"type": "article"
},
{
"title": "Angular SSR Deep Dive (With Client HYDRATION)",
"url": "https://www.youtube.com/watch?v=U1MP4uCuUVI",
"type": "video"
}
]
"description": "",
"links": []
},
"lLa-OnHV6GzkNFZu29BIT": {
"title": "Testing",
"description": "In any software development process, testing the application plays a vital role. If bugs and crashes are not figured out and solved they can defame the development company as well as hurt the clients too. But, Angulars architecture comes with built-in testability features. As soon as you create a new project with Angular CLI, two essential testing tools are installed. They are: Jasmine and Karma. Jasmine is the testing library which structures individual tests into specifications (“specs”) and suites. And Karma is the test runner, which enables the different browsers to run the tests mentioned by Jasmine and the browsers will finally report the test results back.\n\nYou can also unit test an Angular application with other testing libraries and test runners. Each library and runner has its own distinctive installation procedures, configuration, and syntax.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Testing",
"url": "https://angular.dev/guide/testing",
"type": "article"
},
{
"title": "Jasmine Official Docs",
"url": "https://jasmine.github.io/",
"type": "article"
},
{
"title": "Karma Official Docs",
"url": "https://karma-runner.github.io/latest/index.html",
"type": "article"
},
{
"title": "Testing Angular - A Guide to Robust Angular Applications",
"url": "https://testing-angular.com/",
"type": "article"
},
{
"title": "Introduction | Angular Unit Testing Made Easy: A Comprehensive Introduction",
"url": "https://www.youtube.com/watch?v=emnwsVy8wRs",
"type": "video"
}
]
"description": "In any software development process, Testing the application plays a vital role. If Bugs and crashes are not figured out and solved they can defame the development company as well as hurt the clients too. But, Angulars architecture comes with built-in testability features. As soon as you create a new project with Angular CLI, two essential testing tools are installed.They are: Jasmine and Karma. Jasmine is the testing library which structures individual tests into specifications (“specs”) and suites. And Karma is the test runner, which enables the different browsers to run the tests mentioned by Jasmine and the browsers will finally report the test results back.",
"links": []
},
"HU1eTYB321C93qh_U7ioF": {
"title": "Testing Services",
"description": "To ensure your services function as expected, you can write dedicated tests for them. Services are typically the easiest files to unit test. You can instantiate the service within a `beforeEach` block, invoke its methods, and assert the results.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Testing Services",
"url": "https://angular.dev/guide/testing/services",
"type": "article"
},
{
"title": "Step by Step implementation of the Calculator Service with Jasmine Specification",
"url": "https://www.youtube.com/watch?v=yoJDYEq8vSs",
"type": "video"
}
]
"description": "",
"links": []
},
"rH13NBFG02hnn5eABSNCY": {
"title": "Testing Pipes",
@@ -2701,28 +2199,12 @@
},
"4xt0m5jkUqB4Z-krcFBuL": {
"title": "Testing Requests",
"description": "As for any external dependency, you must mock the HTTP backend so your tests can simulate interaction with a remote server. The `@angular/common/http/testing` library provides tools to capture requests made by the application, make assertions about them, and mock the responses to emulate your backend's behavior.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Testing Requests",
"url": "https://angular.dev/guide/http/testing",
"type": "article"
},
{
"title": "Import Http Client Testing Module and make Test call with the HttpClient",
"url": "https://www.youtube.com/watch?v=Sgy_RRXC9As",
"type": "video"
},
{
"title": "HTTP | Angular Unit Testing Made Easy: Comprehensive Guide to HTTP Testing",
"url": "https://www.youtube.com/watch?v=7rlwryYhGzs",
"type": "video"
}
]
"description": "",
"links": []
},
"TGRZBizDy83JKg_MhnRdX": {
"title": "Services with Dependencies",
"description": "When you add a dependency to your service, you must also include it in your tests. For isolated tests, pass an instance of the injectable dependency class into the services constructor. Using the `inject` function can add complexity. Injecting the real service is often impractical because dependent services can be difficult to create and control. Instead, mock the dependency, use a dummy value, or create a spy on the relevant service method. By using the TestBed testing utility, you can let Angulars dependency injection handle service creation and manage constructor argument order.\n\nVisit the following resources to learn more:",
"description": "In an Angular application, Services are responsible for fetching, storing and processing data. Services are singletons, meaning there is only one instance of a Service during runtime. They are fit for central data storage, HTTP and WebSocket communication as well as data validation.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Website",
@@ -2735,19 +2217,9 @@
"type": "article"
},
{
"title": "Testing the Service which has another service injected through Dependency Injection",
"url": "https://www.youtube.com/watch?v=ACb8wqwgOV4",
"type": "video"
},
{
"title": "Testing Services which has HttpClient as dependency by using Jasmine Spy",
"url": "https://www.youtube.com/watch?v=15othucRXcI",
"type": "video"
},
{
"title": "Angular Unit Tests with the inject() function",
"url": "https://www.youtube.com/watch?v=Tvsa4OMUGXs",
"type": "video"
"title": "Explore top posts about Testing",
"url": "https://app.daily.dev/tags/testing?ref=roadmapsh",
"type": "article"
}
]
},
@@ -2790,29 +2262,8 @@
},
"f5v74Uw54LsB4FgdN6eCd": {
"title": "Debugging Tests",
"description": "If your tests aren't working as you expect them to, you can inspect and debug them in the browser. Be sure to set breakpoints to track your application's execution.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Debugging tests",
"url": "https://angular.dev/guide/testing/debugging",
"type": "article"
},
{
"title": "Angular Official Docs - Devtools",
"url": "https://angular.dev/tools/devtools",
"type": "article"
},
{
"title": "Debug Like a Pro: Essential Breakpoint Techniques in Angular",
"url": "https://www.youtube.com/watch?v=Be9Q1cchurQ",
"type": "video"
},
{
"title": "Debug Angular 17 Code in VS Code with Break Points and Extensions",
"url": "https://www.youtube.com/watch?v=r50UXhT9hc0",
"type": "video"
}
]
"description": "",
"links": []
},
"0dYWO_Zvh9J5_6cRjRjvI": {
"title": "Component Templates",
@@ -2879,87 +2330,23 @@
},
"W8OwpEw00xn0GxidlJjdc": {
"title": "Localize Package",
"description": "To take advantage of the localization features of Angular, use the Angular CLI to add the `@angular/localize` package to your project. If `@angular/localize` is not installed and you try to build a localized version of your project (for example, while using the i18n attributes in templates), the Angular CLI will generate an error, which would contain the steps that you can take to enable i18n for your project.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Add the localize package",
"url": "https://angular.dev/guide/i18n/add-package",
"type": "article"
},
{
"title": "Learn to Internationalize Your Angular Apps with @angular/localize",
"url": "https://www.youtube.com/watch?v=5h7HPY1OMfU",
"type": "video"
},
{
"title": "Multi-Language Support in Angular: A Guide to Localization and Internationalization",
"url": "https://www.youtube.com/watch?v=ouI21AyJ9tE",
"type": "video"
}
]
"description": "",
"links": []
},
"dVKl3Z2Rnf6IB064v19Mi": {
"title": "Locales by ID",
"description": "Angular uses the Unicode locale identifier (Unicode locale ID) to find the correct locale data for internationalization of text strings. A locale ID specifies the language, country, and an optional code for further variants or subdivisions. A locale ID consists of the language identifier, a hyphen (-) character, and the locale extension. By default, Angular uses `en-US` as the source locale of your project.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular locales",
"url": "https://github.com/angular/angular/tree/main/packages/common/locales",
"type": "opensource"
},
{
"title": "Angular Official Docs - Refer to locales by ID",
"url": "https://angular.dev/guide/i18n/locale-id",
"type": "article"
},
{
"title": "Codes arranged alphabetically by alpha-3/ISO 639-2 Code",
"url": "https://www.loc.gov/standards/iso639-2/php/code_list.php",
"type": "article"
},
{
"title": "Unicode CLDR Specifications",
"url": "https://cldr.unicode.org/index/cldr-spec",
"type": "article"
}
]
"description": "",
"links": []
},
"jL5amGV1BAX_V5cyTIH7d": {
"title": "Translation Files",
"description": "After you prepare a component for translation, use the `extract-i18n` Angular CLI command to extract the marked text in the component into a source language file. The marked text includes text marked with `i18n`, attributes marked with `i18n`\\-attribute, and text tagged with `$localize`. The `extract-i18n` command creates a source language file named `messages.xlf` in the root directory of your project. If you have multiple language files, add the locale to the file name, like `messages.{locale}.xlf`.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Translation Files",
"url": "https://angular.dev/guide/i18n/translation-files",
"type": "article"
},
{
"title": "Angular Official Docs - Extract i18n",
"url": "https://angular.dev/cli/extract-i18n",
"type": "article"
},
{
"title": "Angular i18n: internationalization & localization with examples",
"url": "https://lokalise.com/blog/angular-i18n/",
"type": "article"
}
]
"description": "",
"links": []
},
"9ISvaaJ815_cr_KW9vQhT": {
"title": "Multiple Locales",
"description": "To deploy an Angular application with multiple locales, follow these steps:\n\n1. Place different versions of your app in locale-specific directories\n2. Use the HTML `<base>` tag with the `href` attribute to set the base URL for relative links.\n3. Deploy each language version in a different subdirectory. Redirect users to their preferred language based on the `Accept-Language` HTTP header.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Angular Official Docs - Deploy Multiple Locales",
"url": "https://angular.dev/guide/i18n/deploy",
"type": "article"
},
{
"title": "How Make Multi-Language Angular Websites - Full Guidance On Angular Localization",
"url": "https://www.youtube.com/watch?v=vSwYuyH4kMA",
"type": "video"
}
]
"description": "",
"links": []
},
"rYJq59Q0YdfK6n3x740Em": {
"title": "Animation",

File diff suppressed because it is too large Load Diff

View File

@@ -3954,7 +3954,7 @@
]
},
"ETEUA7jaEGyOEX8tAVNWs": {
"title": "Processes and Threads",
"title": "Porcesses and Threads",
"description": "Processes and threads are the basic building blocks of a computer program. They are the smallest units of execution in a program. A process is an instance of a program that is being executed. A thread is a sequence of instructions within a process that can be executed independently of other code.\n\nVisit the following resources to learn more:",
"links": [
{

File diff suppressed because it is too large Load Diff

View File

@@ -11,29 +11,8 @@
},
"Lsapbmg-eMIYJAHpV97nO": {
"title": "Types of Data Analytics",
"description": "Data Analytics has proven to be a critical part of decision-making in modern business ventures. It is responsible for discovering, interpreting, and transforming data into valuable information. Different types of data analytics look at past, present, or predictive views of business operations.\n\nData Analysts, as ambassadors of this domain, employ these types, to answer various questions:\n\n* Descriptive Analytics _(what happened in the past?)_\n* Diagnostic Analytics _(why did it happened in the past?)_\n* Predictive Analytics _(what will happen in the future?)_\n* Prescriptive Analytics _(how can we make it happen?)_\n\nUnderstanding these types gives data analysts the power to transform raw datasets into strategic insights.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Data Analytics and its type",
"url": "https://www.geeksforgeeks.org/data-analytics-and-its-type/",
"type": "article"
},
{
"title": "The 4 Types of Data Analysis: Ultimate Guide",
"url": "https://careerfoundry.com/en/blog/data-analytics/different-types-of-data-analysis/",
"type": "article"
},
{
"title": "Descriptive vs Diagnostic vs Predictive vs Prescriptive Analytics: What's the Difference?",
"url": "https://www.youtube.com/watch?v=QoEpC7jUb9k",
"type": "video"
},
{
"title": "Types of Data Analytics",
"url": "https://www.youtube.com/watch?v=lsZnSgxMwBA",
"type": "video"
}
]
"description": "Data Analytics has proven to be a critical part of decision-making in modern business ventures. It is responsible for discovering, interpreting, and transforming data into valuable information. Different types of data analytics look at past, present, or predictive views of business operations.\n\nData Analysts, as ambassadors of this domain, employ these types, which are namely Descriptive Analytics, Diagnostic Analytics, Predictive Analytics and Prescriptive Analytics, to answer various questions — What happened? Why did it happen? What could happen? And what should we do next? Understanding these types gives data analysts the power to transform raw datasets into strategic insights.",
"links": []
},
"hWDh0ooidbqZb000ENVok": {
"title": "Descriptive Analytics",
@@ -157,12 +136,12 @@
"description": "The visualization of data is an essential skill in the toolkit of every data analyst. This practice is about transforming complex raw data into a graphical format that allows for an easier understanding of large data sets, trends, outliers, and important patterns. Whether pie charts, line graphs, bar graphs, or heat maps, data visualization techniques not only streamline data analysis, but also facilitate a more effective communication of the findings to others. This key concept underscores the importance of presenting data in a digestible and visually appealing manner to drive data-informed decision making in an organization.\n\nLearn more from the following resources:",
"links": [
{
"title": "Data visualization beginner's guide",
"title": "Data visualisation beginner's guide",
"url": "https://www.tableau.com/en-gb/learn/articles/data-visualization",
"type": "article"
},
{
"title": "Data Visualization in 2024",
"title": "Data Visualisation in 2024",
"url": "https://www.youtube.com/watch?v=loYuxWSsLNc",
"type": "video"
}
@@ -1355,12 +1334,12 @@
"description": "Parallel processing is an efficient form of data processing that allows Data Analysts to deal with larger volumes of data at a faster pace. It is a computational method that allows multiple tasks to be performed concurrently, instead of sequentially, thus, speeding up data processing. Parallel processing proves to be invaluable for Data Analysts, as they are often tasked with analyzing huge data sets and compiling reports in real-time. As the demand for rapid data processing and quick analytics is on the rise, the technique of parallel processing forms a critical element in the versatile toolkit of a Data Analyst.\n\nLearn more from the following resources:",
"links": [
{
"title": "What Is Parallel Processing?",
"title": "What is parallel processing?",
"url": "https://www.spiceworks.com/tech/iot/articles/what-is-parallel-processing/",
"type": "article"
},
{
"title": "How Parallel Computing Works",
"title": "How parallel computing works?",
"url": "https://computer.howstuffworks.com/parallel-processing.htm",
"type": "article"
}

File diff suppressed because one or more lines are too long

View File

@@ -358,45 +358,13 @@
},
"KdFYmj36M2jrGfsYkukpo": {
"title": "IDEs",
"description": "The concept of Integrated Development Environments (IDEs) traces its roots back to the 1960s and 1970s when basic text editors and simple programming tools were used in early computing. The first notable IDEs emerged with the rise of Unix systems in the 1970s, such as the EMACS text editor, which included features like code editing and debugging. Today, IDEs have become essential for developers, supporting multiple programming languages and integrating cloud-based tools, continuous integration, and real-time collaboration. IDEs like Visual Studio Code, IntelliJ IDEA, and Eclipse are widely adopted by developers across industries.\n\nLearn more from the following resources:",
"links": [
{
"title": "History of IDEs",
"url": "https://multiqos.com/blogs/guide-to-integrated-development-environment/#:~:text=While%20TurboPascal%20may%20have%20popularized,significant%20popularity%20in%20the%201980s.",
"type": "article"
},
{
"title": "Visual Studio Code",
"url": "https://code.visualstudio.com/",
"type": "article"
},
{
"title": "JetBrains IDEs",
"url": "https://www.jetbrains.com/",
"type": "article"
}
]
"description": "",
"links": []
},
"j5nNSYI8s-cH8EA6G1EWY": {
"title": "VS Code",
"description": "Visual Studio Code (VSCode) was first announced by Microsoft in 2015 and quickly became one of the most popular and widely used Integrated Development Environments (IDEs). Built on [Electron](https://www.electronjs.org/), a framework that allows web technologies like JavaScript, HTML, and CSS to create desktop applications, VSCode offers a lightweight and highly extensible platform for developers. VSCode focuses on being a streamlined code editor with the ability to install extensions that add features such as debugging, version control, and language-specific tooling. Microsoft's vision was to create a flexible environment that could cater to all types of developers, from beginners to seasoned professionals.\n\nLearn more from the following resources:",
"links": [
{
"title": "video@FreeCodeCamp Crash Course",
"url": "https://www.youtube.com/watch?v=WPqXP_kLzpo",
"type": "course"
},
{
"title": "video@VS Code basics",
"url": "https://www.youtube.com/watch?v=B-s71n0dHUk",
"type": "article"
},
{
"title": "Visual Studio Code",
"url": "https://code.visualstudio.com/",
"type": "article"
}
]
"description": "",
"links": []
},
"NCnKS435DCl-8vilr1_XE": {
"title": "JetBrains IDEs",

View File

@@ -235,11 +235,36 @@
"title": "HTML",
"description": "HTML stands for HyperText Markup Language. It is used on the frontend and gives the structure to the webpage which you can style using CSS and make interactive using JavaScript.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Interactive HTML Course",
"url": "https://github.com/denysdovhan/learnyouhtml",
"type": "opensource"
},
{
"title": "W3Schools: Learn HTML",
"url": "https://www.w3schools.com/html/html_intro.asp",
"type": "article"
},
{
"title": "htmlreference.io: All HTML elements at a glance",
"url": "https://htmlreference.io/",
"type": "article"
},
{
"title": "HTML For Beginners The Easy Way",
"url": "https://html.com",
"type": "article"
},
{
"title": "Web Development Basics",
"url": "https://internetingishard.netlify.app/html-and-css/index.html",
"type": "article"
},
{
"title": "You don't need JavaScript for that",
"url": "https://www.htmhell.dev/adventcalendar/2023/2/",
"type": "article"
},
{
"title": "Explore top posts about HTML",
"url": "https://app.daily.dev/tags/html?ref=roadmapsh",
@@ -254,6 +279,11 @@
"title": "HTML Full Course - Build a Website Tutorial",
"url": "https://www.youtube.com/watch?v=pQN-pnXPaVg",
"type": "video"
},
{
"title": "HTML Tutorial for Beginners: HTML Crash Course",
"url": "https://www.youtube.com/watch?v=qz0aGYrrlhU",
"type": "video"
}
]
},
@@ -266,15 +296,30 @@
"url": "https://www.w3schools.com/html/html_intro.asp",
"type": "article"
},
{
"title": "MDN Docs: Getting Started with HTML ",
"url": "https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started",
"type": "article"
},
{
"title": "web.dev: Learn HTML",
"url": "https://web.dev/learn/html",
"type": "article"
},
{
"title": "HTML Cheatsheet",
"url": "https://htmlcheatsheet.com",
"type": "article"
},
{
"title": "HTML Full Course - Build a Website Tutorial",
"url": "https://www.youtube.com/watch?v=pQN-pnXPaVg",
"type": "video"
},
{
"title": "HTML Tutorial for Beginners: HTML Crash Course",
"url": "https://www.youtube.com/watch?v=qz0aGYrrlhU",
"type": "video"
}
]
},
@@ -292,6 +337,16 @@
"url": "https://www.w3schools.com/html/html5_semantic_elements.asp",
"type": "article"
},
{
"title": "How To Write Semantic HTML",
"url": "https://hackernoon.com/how-to-write-semantic-html-dkq3ulo",
"type": "article"
},
{
"title": "Semantic HTML: What It Is and How It Improves Your Site",
"url": "https://blog.hubspot.com/website/semantic-html",
"type": "article"
},
{
"title": "Semantic Markup",
"url": "https://html.com/semantic-markup",
@@ -301,6 +356,11 @@
"title": "Semantic HTML - web.dev",
"url": "https://web.dev/learn/html/semantic-html/",
"type": "article"
},
{
"title": "Explore top posts about HTML",
"url": "https://app.daily.dev/tags/html?ref=roadmapsh",
"type": "article"
}
]
},
@@ -339,11 +399,31 @@
"url": "https://www.w3schools.com/accessibility/index.php",
"type": "article"
},
{
"title": "A Complete Guide To Accessible Front-End Components",
"url": "https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/",
"type": "article"
},
{
"title": "MDN Accessibility",
"url": "https://developer.mozilla.org/en-US/docs/Web/Accessibility",
"type": "article"
},
{
"title": "Accessibility for Developers by Google",
"url": "https://web.dev/accessibility",
"type": "article"
},
{
"title": "Web Accessibility by Udacity",
"url": "https://www.udacity.com/course/web-accessibility--ud891",
"type": "article"
},
{
"title": "Accessibility as an Essential Part of the Inclusive Developer Experience",
"url": "https://thenewstack.io/accessibility-as-an-essential-part-of-the-inclusive-developer-experience/",
"type": "article"
},
{
"title": "Explore top posts about Accessibility",
"url": "https://app.daily.dev/tags/accessibility?ref=roadmapsh",
@@ -360,11 +440,31 @@
"title": "SEO Basics",
"description": "SEO or Search Engine Optimization is the technique used to optimize your website for better rankings on search engines such as Google, Bing etc.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "SEO Guide",
"url": "https://github.com/seo/guide",
"type": "opensource"
},
{
"title": "Google Search Central — SEO Docs",
"url": "https://developers.google.com/search/docs",
"type": "article"
},
{
"title": "8 Must-Know SEO Best Practices For Developers",
"url": "https://neilpatel.com/blog/seo-developers/",
"type": "article"
},
{
"title": "SEO for Developers",
"url": "https://medium.com/welldone-software/seo-for-developers-a-quick-overview-5b5b7ce34679",
"type": "article"
},
{
"title": "Learning SEO",
"url": "https://learningseo.io/",
"type": "article"
},
{
"title": "Explore top posts about SEO",
"url": "https://app.daily.dev/tags/seo?ref=roadmapsh",
@@ -386,16 +486,46 @@
"title": "CSS",
"description": "CSS or Cascading Style Sheets is the language used to style the frontend of any website. CSS is a cornerstone technology of the World Wide Web, alongside HTML and JavaScript.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "The Odin Project",
"url": "https://www.theodinproject.com//",
"type": "article"
},
{
"title": "What The Flexbox!",
"url": "https://flexbox.io/",
"type": "article"
},
{
"title": "W3Schools — Learn CSS",
"url": "https://www.w3schools.com/css/",
"type": "article"
},
{
"title": "cssreference.io: All CSS properties at a glance",
"url": "https://cssreference.io/",
"type": "article"
},
{
"title": "Web.dev by Google — Learn CSS",
"url": "https://web.dev/learn/css/",
"type": "article"
},
{
"title": "Learn to Code HTML & CSS",
"url": "https://learn.shayhowe.com/html-css/building-your-first-web-page/",
"type": "article"
},
{
"title": "Joshw Comeaus CSS Hack Blog Posts",
"url": "https://www.joshwcomeau.com/",
"type": "article"
},
{
"title": "100 Days CSS Challenge",
"url": "https://100dayscss.com",
"type": "article"
},
{
"title": "Explore top posts about CSS",
"url": "https://app.daily.dev/tags/css?ref=roadmapsh",
@@ -406,10 +536,20 @@
"url": "https://youtu.be/n4R2E7O-Ngo",
"type": "video"
},
{
"title": "CSS Crash Course For Absolute Beginners",
"url": "https://www.youtube.com/watch?v=yfoY53QXEnI",
"type": "video"
},
{
"title": "HTML and CSS Tutorial",
"url": "https://www.youtube.com/watch?v=D-h8L5hgW-w",
"type": "video"
},
{
"title": "CSS Masterclass - Tutorial & Course for Beginners",
"url": "https://www.youtube.com/watch?v=FqmB-Zj2-PA",
"type": "video"
}
]
},
@@ -453,6 +593,11 @@
"title": "Making Layouts",
"description": "Float, grid, flexbox, positioning, display and box model are some of the key topics that are used for making layouts. Use the resources below to learn about these topics:\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Learn CSS Grid for free",
"url": "https://scrimba.com/learn/cssgrid?via=roadmap",
"type": "course"
},
{
"title": "Learn and Practice Flexbox",
"url": "https://flexboxfroggy.com/",
@@ -540,16 +685,41 @@
"title": "JavaScript",
"description": "JavaScript allows you to add interactivity to your pages. Common examples that you may have seen on the websites are sliders, click interactions, popups and so on.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "You Dont Know JS Yet (book series) ",
"url": "https://github.com/getify/You-Dont-Know-JS",
"type": "opensource"
},
{
"title": "Learn the basics of JavaScript",
"url": "https://github.com/workshopper/javascripting",
"type": "opensource"
},
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "/javascript",
"type": "article"
},
{
"title": "W3Schools JavaScript Tutorial",
"url": "https://www.w3schools.com/js/",
"type": "article"
},
{
"title": "The Modern JavaScript Tutorial",
"url": "https://javascript.info/",
"type": "article"
},
{
"title": "Learn JavaScript: Covered many topics",
"url": "https://www.javascripttutorial.net/",
"type": "article"
},
{
"title": "Eloquent JavaScript textbook",
"url": "https://eloquentjavascript.net/",
"type": "article"
},
{
"title": "Build 30 Javascript projects in 30 days",
"url": "https://javascript30.com/",
@@ -564,6 +734,11 @@
"title": "JavaScript Crash Course for Beginners",
"url": "https://youtu.be/hdI2bqOjy3c?t=2",
"type": "video"
},
{
"title": "Build a Netflix Landing Page Clone with HTML, CSS & JS",
"url": "https://youtu.be/P7t13SGytRk?t=22",
"type": "video"
}
]
},
@@ -1911,16 +2086,46 @@
"title": "TypeScript",
"description": "TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Scrimba — TypeScript Basics",
"url": "https://scrimba.com/learn/typescript?via=roadmap",
"type": "course"
},
{
"title": "Official Website",
"url": "https://www.typescriptlang.org/",
"type": "article"
},
{
"title": "Official Docs for Deep Dives",
"url": "https://www.typescriptlang.org/docs/",
"type": "article"
},
{
"title": "The TypeScript Handbook",
"url": "https://www.typescriptlang.org/docs/handbook/intro.html",
"type": "article"
},
{
"title": "TypeScript Tutorial",
"url": "https://www.tutorialspoint.com/typescript/index.htm",
"type": "article"
},
{
"title": "What Is TypeScript?",
"url": "https://thenewstack.io/what-is-typescript/",
"type": "article"
},
{
"title": "TypeScript Tutorial: Go beyond Hello, World!",
"url": "https://thenewstack.io/typescript-tutorial-go-beyond-hello-world/",
"type": "article"
},
{
"title": "A Guide to Using the Programming Language",
"url": "https://thenewstack.io/typescript-tutorial-a-guide-to-using-the-programming-language/",
"type": "article"
},
{
"title": "Explore top posts about TypeScript",
"url": "https://app.daily.dev/tags/typescript?ref=roadmapsh",
@@ -2470,11 +2675,6 @@
"url": "https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/",
"type": "article"
},
{
"title": "Building a simple offline-capable Notepad app ",
"url": "https://www.amitmerchant.com/Building-Simple-Offline-Notepad-Using-Service-Worker/",
"type": "article"
},
{
"title": "Explore top posts about Web Development",
"url": "https://app.daily.dev/tags/webdev?ref=roadmapsh",

View File

@@ -3,6 +3,11 @@
"title": "HTML",
"description": "HTML stands for HyperText Markup Language. It is used on the frontend and gives the structure to the webpage which you can style using CSS and make interactive using JavaScript.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Interactive HTML Course",
"url": "https://github.com/denysdovhan/learnyouhtml",
"type": "opensource"
},
{
"title": "W3Schools: Learn HTML",
"url": "https://www.w3schools.com/html/html_intro.asp",
@@ -14,13 +19,18 @@
"type": "article"
},
{
"title": "HTML Full Course for Beginners",
"url": "https://youtu.be/mJgBOIoGihA",
"title": "HTML Full Course - Build a Website Tutorial",
"url": "https://www.youtube.com/watch?v=pQN-pnXPaVg",
"type": "video"
},
{
"title": "HTML Full Course - Build a Website Tutorial",
"url": "https://www.youtube.com/watch?v=pQN-pnXPaVg",
"title": "HTML Tutorial for Beginners: HTML Crash Course",
"url": "https://www.youtube.com/watch?v=qz0aGYrrlhU",
"type": "video"
},
{
"title": "HTML and CSS Full Course - Beginner To Pro",
"url": "https://youtu.be/a_iQb1lnAEQ",
"type": "video"
}
]
@@ -35,8 +45,13 @@
"type": "article"
},
{
"title": "Web.dev by Google — Learn CSS",
"url": "https://web.dev/learn/css/",
"title": "Learn to Code HTML & CSS",
"url": "https://learn.shayhowe.com/html-css/building-your-first-web-page/",
"type": "article"
},
{
"title": "What The Flexbox!",
"url": "https://flexbox.io/",
"type": "article"
},
{
@@ -45,24 +60,29 @@
"type": "article"
},
{
"title": "CSS Complete Course",
"url": "https://youtu.be/n4R2E7O-Ngo",
"title": "CSS Crash Course For Absolute Beginners",
"url": "https://www.youtube.com/watch?v=yfoY53QXEnI",
"type": "video"
},
{
"title": "HTML and CSS Tutorial",
"url": "https://www.youtube.com/watch?v=D-h8L5hgW-w",
"type": "video"
},
{
"title": "CSS Masterclass - Tutorial & Course for Beginners",
"url": "https://www.youtube.com/watch?v=FqmB-Zj2-PA",
"type": "video"
}
]
},
"T9PB6WQf-Fa9NXKKvVOy_": {
"title": "JavaScript",
"description": "JavaScript allows you to add interactivity to your pages. Common examples that you may have seen on the websites are sliders, click interactions, popups and so on.\n\nVisit the following resources to learn more:",
"description": "JavaScript allows you to add interactivity to your pages. Common examples that you may have seen on the websites are sliders, click interactions, popups and so on.\n\nVisit the following resources to learn more:\n\nWe also have this [JavaScript roadmap](/javascript). You don't need to follow it right now, just learn from some courses and revisit the roadmap later in your journey.",
"links": [
{
"title": "Visit Dedicated JavaScript Roadmap",
"url": "/javascript",
"title": "W3Schools JavaScript Tutorial",
"url": "https://www.w3schools.com/js/",
"type": "article"
},
{
@@ -71,8 +91,8 @@
"type": "article"
},
{
"title": "Build 30 Javascript projects in 30 days",
"url": "https://javascript30.com/",
"title": "Exploring JS: JavaScript books for programmers",
"url": "https://exploringjs.com/",
"type": "article"
},
{
@@ -84,6 +104,11 @@
"title": "JavaScript Crash Course for Beginners",
"url": "https://youtu.be/hdI2bqOjy3c?t=2",
"type": "video"
},
{
"title": "Build a Netflix Landing Page Clone with HTML, CSS & JS",
"url": "https://youtu.be/P7t13SGytRk",
"type": "video"
}
]
},

View File

@@ -378,17 +378,12 @@
"links": []
},
"AaRZiItRcn8fYb5R62vfT": {
"title": "GDScript",
"description": "GDScript is a high-level, dynamically-typed programming language designed specifically for the Godot Engine, an open-source game development platform. It is tailored for ease of use and rapid development of game logic and functionality. GDScript features a syntax similar to Python, which simplifies learning and coding for developers familiar with Python, while providing direct access to Godot's rich set of built-in functions and game-specific APIs. The language integrates closely with Godot's scene system and scripting environment, enabling developers to create and manipulate game objects, handle input, and control game behavior efficiently.\n\nLearn more from the following resources:",
"title": "Assembly",
"description": "**Assembly** is a low-level programming language, often used for direct hardware manipulation, real-time systems, and to write performance-critical code. It provides a strong correspondence between its instructions and the architecture's machine-code instructions, since it directly represents the specific commands of the computer's CPU structure. However, it's closer to machine language (binary code) than to human language, which makes it difficult to read and understand. The syntax varies greatly, which depends upon the CPU architecture for which it's designed, thus Assembly language written for one type of processor can't be used on another. Despite its complexity, time-intensive coding process and machine-specific nature, Assembly language is still utilized for speed optimization and hardware manipulation where high-level languages may not be sufficient.",
"links": [
{
"title": "GDScript Website",
"url": "https://gdscript.com/",
"type": "article"
},
{
"title": "How to program in Godot - GDScript Tutorial",
"url": "https://www.youtube.com/watch?v=e1zJS31tr88",
"title": "Code walkthrough of a game written in x64 assembly",
"url": "https://www.youtube.com/watch?v=WUoqlp30M78",
"type": "video"
}
]
@@ -462,7 +457,7 @@
},
"odfZWKtPbb-lC35oeTCNV": {
"title": "Specular",
"description": "`Specular` reflections are mirror-like reflections. In these cases, the rays of light are reflected, more than they are absorbed. The angle of incidence is equal to the angle of reflection, that is to say that the angle at which the light enters the medium and then bounces off, the angle of the beam that bounced off would be the same.\n\nLearn more from the following resources:\n\n\\-[@video@Specular reflection](https://www.youtube.com/watch?v=2cFvJkc4pQk)",
"description": "",
"links": []
},
"THMmnx8p_P0X-dSPoHvst": {
@@ -472,35 +467,13 @@
},
"iBZ1JsEWI0xuLgUvfWfl-": {
"title": "Texture",
"description": "`Texture` is the visual quality of an object. Where the `mesh` determines the shape or `topology` of an object, the texture describes the quality of said object. For instance, if there is a spherical mesh, is it supposed to be shiny? is it supposed to be rough? is it supposed to be of rock or of wood? questions of this ilk are often resolved using textures. Textures are often just 2D images that are wrapped onto 3D meshes. The 3D mesh is first divided into segments and unfurled; the 3D meshes are converted into 2D chunks, this process is known as `UV Unwrapping`. Once a mesh has been unwrapped, the textures in the form of an image are applied to the 2D chunks of the 3D mesh, this way the texture knows how to properly wrap around the mesh and avoid any conflicts. Textures determine the visual feel and aesthetics of the game.\n\nLearn more from the following resources:",
"links": [
{
"title": "How Nintendo textures work",
"url": "https://www.youtube.com/watch?v=WrCMzHngLxI",
"type": "video"
},
{
"title": "How Pixar textures work",
"url": "https://www.youtube.com/watch?v=o_I6jxlN-Ck",
"type": "video"
}
]
"description": "",
"links": []
},
"r4UkMd5QURbvJ3Jlr_H9H": {
"title": "Bump",
"description": "`Bump` is very similar to texture. It is, as a matter of fact, a type of texture itself. If you take the texture of a bricked wall, it will becoming increasingly obvious that the amount of detail present inside the wall, if geometrically processed would be incredibly demanding and wasteful. In order to combat this ineffeciency, the `bump` maps were created. Traditionally, a flat texture would just be an image of something called a `color map`, that is to say, where does each individual color of the pixel should be to represent a texture. When you take the picture of your floor, wall, or any object, that image in essence is the color map. The bump map is different as it informs the texture about it's `normal` values. So, if you take a flat 2D mesh and apply a bump map on it, it will render the same 2D mesh with all the normal values baked into the flat 2D mesh, creating a graphically effect mimicking 3-dimensionality.\n\nThere is also something known as a normal map, and displacement maps.\n\nLearn more from the following resources:",
"links": [
{
"title": "Normals, Normal maps and Bump maps",
"url": "https://www.youtube.com/watch?v=l5PYyzsZED8",
"type": "video"
},
{
"title": "Bump, normal and displacement",
"url": "https://www.youtube.com/watch?v=43Ilra6fNGc",
"type": "video"
}
]
"description": "",
"links": []
},
"YGeGleEN203nokiZIYJN8": {
"title": "Parallax",
@@ -799,25 +772,13 @@
},
"sz1047M8_kScjth84yPwU": {
"title": "Decision Tree Learning",
"description": "`Decision Tree Learning` is an important concept in game development, particularly in the development of artificial intelligence for game characters. It is a kind of machine learning method that is based on using decision tree models to predict or classify information. A decision tree is a flowchart-like model, where each internal node denotes a test on an attribute, each branch represents an outcome of that test, and each leaf node holds a class label (decision made after testing all attributes). By applying decision tree learning models, computer-controlled characters can make decisions based on different conditions or states. They play a key role in creating complex and interactive gameplay experiences, by enabling game characters to adapt to the player's actions and the ever-changing game environment.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Decision trees - A friendly introduction",
"url": "https://www.youtube.com/watch?v=HkyWAhr9v8g",
"type": "video"
}
]
"description": "`Decision Tree Learning` is an important concept in game development, particularly in the development of artificial intelligence for game characters. It is a kind of machine learning method that is based on using decision tree models to predict or classify information. A decision tree is a flowchart-like model, where each internal node denotes a test on an attribute, each branch represents an outcome of that test, and each leaf node holds a class label (decision made after testing all attributes). By applying decision tree learning models, computer-controlled characters can make decisions based on different conditions or states. They play a key role in creating complex and interactive gameplay experiences, by enabling game characters to adapt to the player's actions and the ever-changing game environment.",
"links": []
},
"ltkEyfuDxExs7knqs79ya": {
"title": "Deep Learning",
"description": "Deep Learning is a sub-field of machine learning, inspired by the structure and function of the human brain, specifically designed to process complex input/output transformations. It uses artificial neural networks with many layers (hence the term 'deep' learning) to model complex, non-linear hypotheses and discover hidden patterns within large datasets. Deep learning techniques are crucial in game development, primarily in creating intelligent behaviors and features in gaming agents, procedural content generation, and player profiling. You might have heard about the uses of deep learning technologies in popular, cutting-edge games like Google DeepMind's AlphaGo. Coding languages like Python, R, and frameworks like TensorFlow, Keras, and PyTorch are commonly used for deep learning tasks. Learning Deep Learning can be a prominent game-changer in your game development journey.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "But what is a neural network? | Chapter 1, Deep learning",
"url": "https://www.youtube.com/watch?v=aircAruvnKk",
"type": "video"
}
]
"description": "Deep Learning is a sub-field of machine learning, inspired by the structure and function of the human brain, specifically designed to process complex input/output transformations. It uses artificial neural networks with many layers (hence the term 'deep' learning) to model complex, non-linear hypotheses and discover hidden patterns within large datasets. Deep learning techniques are crucial in game development, primarily in creating intelligent behaviors and features in gaming agents, procedural content generation, and player profiling. You might have heard about the uses of deep learning technologies in popular, cutting-edge games like Google DeepMind's AlphaGo. Coding languages like Python, R, and frameworks like TensorFlow, Keras, and PyTorch are commonly used for deep learning tasks. Learning Deep Learning can be a prominent game-changer in your game development journey.",
"links": []
},
"AoH2r4EOHyZd8YaV24rBk": {
"title": "Artificial Neural Network",
@@ -893,13 +854,7 @@
},
"YrQgfjsdLCIUxrwflpEHO": {
"title": "Microsurface Scattering",
"description": "Microsurface scattering, also known as sub-surface scattering, is an important phenomenon in Physically Based Rendering (PBR). This process involves the penetration of light into the surface of a material, where it is scattered by interacting with the material. In other words, when light strikes an object, rather than simply bouncing off the surface, some of it goes into the object and gets scattered around inside before getting re-emitted. It is key to achieving more realistic rendering of translucent materials like skin, marble, milk, and more. Consider it essential for replicating how light interacts with real-world materials in a convincing manner in your game.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "The 4 main types of subsurface scattering",
"url": "https://www.youtube.com/watch?v=GkjvYSbGHg4",
"type": "video"
}
]
"description": "Microsurface scattering, also known as sub-surface scattering, is an important phenomenon in Physically Based Rendering (PBR). This process involves the penetration of light into the surface of a material, where it is scattered by interacting with the material. In other words, when light strikes an object, rather than simply bouncing off the surface, some of it goes into the object and gets scattered around inside before getting re-emitted. It is key to achieving more realistic rendering of translucent materials like skin, marble, milk, and more. Consider it essential for replicating how light interacts with real-world materials in a convincing manner in your game.",
"links": []
}
}

View File

@@ -13,11 +13,6 @@
"url": "https://www.datacamp.com/blog/all-about-git",
"type": "article"
},
{
"title": "Version Control (Git) - The Missing Semester of Your CS Education",
"url": "https://missing.csail.mit.edu/2020/version-control/",
"type": "article"
},
{
"title": "GUI Clients",
"url": "https://git-scm.com/downloads/guis",
@@ -220,11 +215,6 @@
"title": ".gitignore",
"description": "Ignored files are tracked in a special file named `.gitignore` that is checked in at the root of your repository. There is no explicit git ignore command: instead the `.gitignore` file must be edited and committed by hand when you have new files that you wish to ignore. `.gitignore` files contain patterns that are matched against file names in your repository to determine whether or not they should be ignored.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "gitignore - A collection of useful .gitignore templates",
"url": "https://github.com/github/gitignore",
"type": "opensource"
},
{
"title": "gitignore Documentation",
"url": "https://git-scm.com/docs/gitignore/en",
@@ -972,21 +962,10 @@
}
]
},
"lONqOqD-4slxa9B5i9ADX": {
"github-wikis@lONqOqD-4slxa9B5i9ADX.md": {
"title": "GitHub Wikis",
"description": "GitHub Wikis are collaborative documentation spaces integrated directly into GitHub repositories. They provide a platform for teams to create, edit, and organize project-related information, such as documentation, guidelines, and FAQs. Wikis support Markdown formatting, making it easy to structure content and include images or links. With version control and the ability to clone wiki repositories, teams can collaboratively maintain up-to-date documentation alongside their code, enhancing project understanding and facilitating knowledge sharing among contributors and users.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "About Wikis",
"url": "https://docs.github.com/en/communities/documenting-your-project-with-wikis/about-wikis",
"type": "article"
},
{
"title": "Documenting your project with Wikis",
"url": "https://docs.github.com/en/communities/documenting-your-project-with-wikis",
"type": "article"
}
]
"description": "",
"links": []
},
"i3AbARgzQtxtlB-1AS8zv": {
"title": "Clean Git History",

View File

@@ -4,7 +4,7 @@
"description": "JavaScript, often abbreviated JS, is a programming language that is one of the core technologies of the World Wide Web, alongside HTML and CSS. It lets us add interactivity to pages e.g. you might have seen sliders, alerts, click interactions, popups, etc on different websites -- all of that is built using JavaScript. Apart from being used in the browser, it is also used in other non-browser environments as well such as Node.js for writing server-side code in JavaScript, Electron for writing desktop applications, React Native for mobile applications, and so on.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "You Don't Know JS Yet (book series)",
"title": "You Dont Know JS Yet (book series)",
"url": "https://github.com/getify/You-Dont-Know-JS",
"type": "opensource"
},
@@ -200,7 +200,7 @@
"type": "article"
},
{
"title": "Understanding Hoisting",
"title": "Understanding hoisting ",
"url": "https://www.digitalocean.com/community/tutorials/understanding-hoisting-in-javascript",
"type": "article"
},
@@ -307,7 +307,7 @@
},
"q85z6x1Lc-yLWepwtIT2_": {
"title": "const",
"description": "Constants are block-scoped, much like variables declared using the `let` keyword. The value of a constant can't be changed through reassignment (i.e. by using the assignment operator), and it can't be re-declared (i.e. through a variable declaration). However, if a constant is an object or array its properties or items can be updated or removed.\n\nVisit the following resources to learn more:",
"description": "Constants are block-scoped, much like variables declared using the `let` keyword. The value of a constant can't be changed through reassignment (i.e. by using the assignment operator), and it can't be redeclared (i.e. through a variable declaration). However, if a constant is an object or array its properties or items can be updated or removed.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "JavaScript Constants - CodeGuage",
@@ -360,7 +360,7 @@
},
"oC4o6GLEES_nUgCJu9Q6I": {
"title": "Global",
"description": "Variables declared Globally (outside any function) have Global Scope. Global variables can be accessed from anywhere in a JavaScript program. Variables declared with `var`, `let` and `const` are quite similar when declared outside a block.\n\nNote\n----\n\nIf you assign a value to a variable that has not been declared i.e `potato = true` it will automatically become a _GLOBAL_ variable.\n\nVisit the following resources to learn more:",
"description": "Variables declared Globally (outside any function) have Global Scope. Global variables can be accessed from anywhere in a JavaScript program. Variables declared with `var`, `let` and `const` are quite similar when declared outside a block.\n\n### Note:\n\nIf you assign a value to a variable that has not been declared i.e `potato = true` it will automatically become a _GLOBAL_ variable.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "JavaScript Scope",
@@ -429,11 +429,6 @@
"title": "String",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String",
"type": "article"
},
{
"title": "JavaScript Strings",
"url": "https://javascript.info/string",
"type": "article"
}
]
},
@@ -450,7 +445,7 @@
},
"GZ_SXsWmP7AsXRTc4WUMw": {
"title": "number",
"description": "The `Number` data type in JavaScript represents floating-point numbers, such as 37 or -9.25. The `Number` constructor provides constants and methods to work with numbers, and values of other types can be converted to numbers using the `Number()` function.\n\nExample\n-------\n\n let num1 = 255; // integer\n let num2 = 255.0; // floating-point number with no fractional part\n let num3 = 0xff; // hexadecimal notation\n let num4 = 0b11111111; // binary notation\n let num5 = 0.255e3; // exponential notation\n \n console.log(num1 === num2); // true\n console.log(num1 === num3); // true\n console.log(num1 === num4); // true\n console.log(num1 === num5); // true\n \n\nIn this example:\n\n* `255` and `255.0` are equivalent, as JavaScript treats both as the same number.\n* `0xff` represents `255` in hexadecimal notation.\n* `0b11111111` represents `255` in binary notation.\n* `0.255e3` is `255` in exponential notation.\n* All these different representations are equal to `255` in JavaScript.",
"description": "The `Number` data type in JavaScript represents floating-point numbers, such as 37 or -9.25. The `Number` constructor provides constants and methods to work with numbers, and values of other types can be converted to numbers using the `Number()` function.\n\n### Example\n\n let num1 = 255; // integer\n let num2 = 255.0; // floating-point number with no fractional part\n let num3 = 0xff; // hexadecimal notation\n let num4 = 0b11111111; // binary notation\n let num5 = 0.255e3; // exponential notation\n \n console.log(num1 === num2); // true\n console.log(num1 === num3); // true\n console.log(num1 === num4); // true\n console.log(num1 === num5); // true\n \n\nIn this example:\n\n* `255` and `255.0` are equivalent, as JavaScript treats both as the same number.\n* `0xff` represents `255` in hexadecimal notation.\n* `0b11111111` represents `255` in binary notation.\n* `0.255e3` is `255` in exponential notation.\n* All these different representations are equal to `255` in JavaScript.",
"links": []
},
"6lUF0neW1piiP1RsaVxEX": {
@@ -585,12 +580,12 @@
"description": "You can use the typeOf operator to find the data type of a JavaScript variable. It returns a string indicating the type of provided operand's value.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "typeof Reference",
"title": "Typeof Reference",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof",
"type": "article"
},
{
"title": "typeof Live Examples",
"title": "Typeof Live Examples",
"url": "https://www.w3schools.com/js/tryit.asp?filename=tryjs_typeof_all",
"type": "article"
}
@@ -700,7 +695,7 @@
"type": "article"
},
{
"title": "What you need to know about JavaScript Implicit Coercion",
"title": "What you need to know about Javascripts Implicit Coercion",
"url": "https://dev.to/promisetochi/what-you-need-to-know-about-javascripts-implicit-coercion-e23",
"type": "article"
}
@@ -1250,7 +1245,7 @@
},
"-z-4VTaC3tOThqChgyoMs": {
"title": "Error Objects",
"description": "When a runtime error occurs, a new `Error` object is created and thrown. With this `Error` object, we can determine the type of the Error and handle it according to its type.\n\nTypes of Errors\n---------------\n\nBesides error constructors, Javascript also has other core Error constructors. Like\n\n* AggregateError - A collection of errors thrown simultaneously.\n* EvalError - An error occurred during the evaluation of a JavaScript expression.\n* InternalError - An internal JavaScript error, often indicating a bug in the engine.\n* RangeError - A value is outside the allowed range for a given operation.\n* ReferenceError - A variable or object is referenced before it's declared or doesn't exist.\n* SyntaxError - The code contains incorrect syntax, preventing it from being parsed.\n\nExample\n-------\n\n try {\n willGiveErrorSometime();\n } catch (error) {\n if (error instanceof RangeError) {\n rangeErrorHandler(error);\n } else if (error instanceof ReferenceError) {\n referenceErrorHandle(error);\n } else {\n errorHandler(error);\n }\n }\n \n\nVisit the following resources to learn more:",
"description": "When a runtime error occurs, a new `Error` object is created and thrown. With this `Error` object, we can determine the type of the Error and handle it according to its type.\n\nTypes of Errors:\n----------------\n\nBesides error constructors, Javascript also has other core Error constructors.\n\n* [@article@AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError)\n* [@article@EvalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError)\n* [@article@InternalError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError)\n* [@article@RangeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError)\n* [@article@ReferenceError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError)\n* [@article@SyntaxError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError)\n\nExample\n-------\n\n try {\n willGiveErrorSometime();\n } catch (error) {\n if (error instanceof RangeError) {\n rangeErrorHandler(error);\n } else if (error instanceof ReferenceError) {\n referenceErrorHandle(error);\n } else {\n errorHandler(error);\n }\n }\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "Error Object - MDN",
@@ -1261,36 +1256,6 @@
"title": "Control flow & Error handling - MDN",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling",
"type": "article"
},
{
"title": "AggregateError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError",
"type": "article"
},
{
"title": "EvalError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError",
"type": "article"
},
{
"title": "InternalError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError",
"type": "article"
},
{
"title": "RangeError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError",
"type": "article"
},
{
"title": "ReferenceError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError",
"type": "article"
},
{
"title": "SyntaxError",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError",
"type": "article"
}
]
},
@@ -1450,7 +1415,7 @@
]
},
"k9rSR-YQ8B_iRcXNm2btP": {
"title": "Unary Operators",
"title": "Unary Opeartors",
"description": "JavaScript Unary Operators are the special operators that consider a single operand and perform all the types of operations on that single operand. These operators include unary plus, unary minus, prefix increments, postfix increments, prefix decrements, and postfix decrements.\n\nVisit the following resources to learn more:",
"links": [
{
@@ -1515,7 +1480,7 @@
},
"fr0NChxMXLpJizyMhXcXS": {
"title": "Arrow Functions",
"description": "Arrow Function is a new way of creating functions with the '=>' operator with a shorter syntax.\n\nExample\n-------\n\n const sayHello = () => {\n console.log(`Hello from Arrow Function !`);\n }\n \n\nVisit the following resources to learn more:",
"description": "Arrow Function is a new way of creating functions with the '=>' operator with a shorter syntax.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "MDN - Arrow Function Expressions",
@@ -1531,7 +1496,7 @@
},
"YZlCoPvZuX5MmpLOTj5d4": {
"title": "IIFEs",
"description": "Immediately-Invoked Function Expression is a function that is executed immediately after it is created.\n\nExample\n-------\n\n // An Async IIFE\n ( async() => {\n \n const x = 1;\n const y = 9;\n \n console.log(`Hello, The Answer is ${x+y}`);\n \n })();\n \n\nVisit the following resources to learn more:",
"description": "Immediately-Invoked Function Expression is a function that is executed immediately after it is created.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "IIFE — MDN Docs",
@@ -1817,43 +1782,21 @@
"description": "The `call()` method allows you to invoke a function with a given `this` value, and arguments provided individually.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Call Method - MDN Docs",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call",
"title": "Understanding Explicit Binding in JavaScript: Call, Bind, and Apply Methods",
"url": "https://medium.com/@amitsharma_24072/understanding-explicit-binding-in-javascript-call-bind-and-apply-methods-7b6ed0107628",
"type": "article"
}
]
},
"-BtF34cEzI6J8sZCDRlRE": {
"title": "apply",
"description": "The apply() method of Function instances calls this function with a given this value, and arguments provided as an array (or an array-like object).\n\nVisit the following resources to learn more:",
"links": [
{
"title": "apply() - MDN",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply",
"type": "article"
}
]
"description": "",
"links": []
},
"dbercnxXVTJXMpYSDNGb2": {
"title": "bind",
"description": "The `bind()` method in JavaScript allows you to create a new function with a specific context and optionally preset arguments. Unlike `call()` or `apply()`, `bind()` does not immediately invoke the function. Instead, it returns a new function that can be called later, either as a regular function or with additional arguments. This is particularly useful when you want to ensure that a function retains a specific context, regardless of how or when it's invoked.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "bind()",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind",
"type": "article"
},
{
"title": "Function binding",
"url": "https://javascript.info/bind",
"type": "article"
},
{
"title": "Javascript Function Bind()",
"url": "https://www.w3schools.com/js/js_function_bind.asp",
"type": "article"
}
]
"description": "",
"links": []
},
"sFOqx6_7poVIVuXhJVY0E": {
"title": "Asynchronous JavaScript",
@@ -1998,7 +1941,7 @@
},
"PJSdqvh5OBwPCNpn3q_S5": {
"title": "Callback Hell",
"description": "The callback hell is when we try to write asynchronous JavaScript in a way where execution happens visually from top to bottom, creating a code that has a pyramid shape with many **})** at the end.\n\nVisit the following resources to learn more:",
"description": "The callback hell is when we try to write asynchronous JavaScript in a way where execution happens visually from top to bottom, creating a code that has a pyramid shape with many }) at the end.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Callbacks in Callbacks - Pyramid of Doom",
@@ -2061,7 +2004,7 @@
},
"kL5rfWxXe4J44ENru1uJS": {
"title": "Fetch",
"description": "The `fetch()` method in JavaScript is used to request to the server and load the information on the webpages. The request can be of any APIs that return the data of the format JSON or XML. This method returns a promise.\n\nVisit the following resources to learn more:",
"description": "The fetch() method in JavaScript is used to request to the server and load the information on the webpages. The request can be of any APIs that return the data of the format JSON or XML. This method returns a promise.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Fetch MDN Docs",
@@ -2077,11 +2020,6 @@
"title": "Network request - Fetch",
"url": "https://javascript.info/fetch",
"type": "article"
},
{
"title": "Abort a fetch request manually in JavaScript",
"url": "https://www.amitmerchant.com/abort-fetch-request-manually-in-javascript/",
"type": "article"
}
]
},
@@ -2181,7 +2119,7 @@
},
"4EXeGkOpfAViB9Uo4zL6O": {
"title": "CommonJS",
"description": "CommonJS modules are the original way to package JavaScript code for Node.js. Node.js also supports the ESModules standard used by browsers and other JavaScript run-times, but CJS is still widely used in backend Node.js applications. Sometimes these modules will be written with a .cjs extension.\n\nVisit the following resources to learn more:",
"description": "CommonJS modules are the original way to package JavaScript code for Node.js. Node.js also supports the ESModules standard used by browsers and other JavaScript runtimes, but CJS is still widely used in backend Node.js applications. Sometimes these modules will be written with a .cjs extension.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "How the CJS Module System Works",
@@ -2265,7 +2203,7 @@
},
"bhuGtcyqPFKu-900aESYz": {
"title": "DOM APIs",
"description": "With HTML DOM, JavaScript can access and change all the elements of an HTML document such as its attributes, CSS styles, remove elements, add and create new elements on the page. Web API means application programming interface for the web. All browsers have a set of built-in Web APIs to support complex operations, and to help accessing data. Like Geo-location API, Web Storage, Web History and others.\n\nVisit the following resources to learn more:",
"description": "With HTML DOM, JavaScript can access and change all the elements of an HTML document such as its attributes, CSS styles, remove elements, add and create new elements on the page. Web API means application programming inteface for the web. All browsers have a set og built-in Web APIs to support complex operations, and to help accessing data. Like Geolocation API, Web Storage, Web History and others.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "DOM- MDN Docs",
@@ -2297,16 +2235,11 @@
},
"rc5WzBBOm2cus-rQl8EOE": {
"title": "Using Browser DevTools",
"description": "These are a set of tools built into the browser to aid frontend developers diagnose and solve various issues in their applications — such as JavaScript and logical bugs, CSS styling issues or even just making quick temporary alterations to the DOM.\n\nTo enter the dev tools, right click and click **Inspect** (or press `ctrl+shift+c`/`cmd+opt+c`) to enter the Elements panel. Here you can debug CSS and HTML issues. If you want to see logged messages or interact with javascript, enter the **Console** tab from the tabs above (or press `ctrl+shift+j` or `F12` / `cmd+opt+j` to enter it directly). Another very useful feature in the Chrome dev tools is the Lighthouse (for checking performance).\n\nNOTE: This isn't a chrome-specific feature, and most browsers (Chromium based or otherwise) will have their own, largely-similar set of devtools.\n\nVisit the following resources to learn more:",
"description": "These are a set of tools built into the browser to aid frontend developers diagnose and solve various issues in their applications — such as JavaScript and logical bugs, CSS styling issues or even just making quick temporary alterations to the DOM.\n\nTo enter the dev tools, right click and click **Inspect** (or press `ctrl+shift+c`/`cmd+opt+c`) to enter the Elements panel. Here you can debug CSS and HTML issues. If you want to see logged messages or interact with javascript, enter the **Console** tab from the tabs above (or press `ctrl+shift+j` or `F12` /`cmd+opt+j` to enter it directly). Another very useful feature in the Chrome dev tools is the Lighthouse (for checking performance).\n\nNOTE: This isn't a chrome-specific feature, and most browsers (Chromium based or otherwise) will have their own, largely-similar set of devtools.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Official Docs",
"url": "https://developer.chrome.com/docs/devtools/",
"type": "article"
},
{
"title": "Debug JavaScript with Chrome Dev Tools",
"url": "https://developer.chrome.com/docs/devtools/javascript/",
"url": "https://developer.chrome.com/docs/devtools/overview/",
"type": "article"
},
{
@@ -2337,7 +2270,7 @@
"type": "article"
},
{
"title": "Effective Javascript Debugging",
"title": "Effective Javascript Debugging ",
"url": "https://medium.com/swlh/effective-javascript-debugging-memory-leaks-75059b2436f6",
"type": "article"
},
@@ -2360,7 +2293,7 @@
},
"ECxISKUAU7js_JsfSHzud": {
"title": "Debugging Performance",
"description": "Enter the dev tools and check out the Lighthouse tab. This is essentially a series of tests which analyses the currently open website on a bunch of metrics related to performance, page speed, accessibility, etc. Feel free to run the tests by clicking the **Analyze Page Load** button (you might want to do this in an incognito tab to avoid errors arising from extensions you're using). Once you have the results, take your time and read through them (and do click through to the reference pages mentioned alongside each test result to know more about it!)\n\nVisit the following resources to learn more:",
"description": "Enter the dev tools and check out the Lighthouse tab. This is essentially a series of tests which analyses the currently open website on a bunch of metrics related to performance, page speed, accessibility, etc. Feel free to run the tests by clicking the **Analyse Page Load** button (you might want to do this in an incognito tab to avoid errors arising from extensions you're using). Once you have the results, take your time and read through them (and do click through to the reference pages mentioned alongside each test result to know more about it!)\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Analyze runtime performance",

View File

@@ -351,13 +351,8 @@
},
"UljuqA89_SlCSDWWMD_C_": {
"title": "Spark",
"description": "Apache Spark is an open-source distributed computing system designed for big data processing and analytics. It offers a unified interface for programming entire clusters, enabling efficient handling of large-scale data with built-in support for data parallelism and fault tolerance. Spark excels in processing tasks like batch processing, real-time data streaming, machine learning, and graph processing. Its known for its speed, ease of use, and ability to process data in-memory, significantly outperforming traditional MapReduce systems. Spark is widely used in big data ecosystems for its scalability and versatility across various data processing tasks.\n\nVisit the following resources to learn more:",
"description": "Apache Spark is an open-source distributed computing system used for big data processing and analytics. It provides an interface for programming entire clusters with implicit data parallelism and fault tolerance.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "ApacheSpark",
"url": "https://spark.apache.org/documentation.html",
"type": "article"
},
{
"title": "Spark By Examples",
"url": "https://sparkbyexamples.com",

View File

@@ -1616,11 +1616,6 @@
"title": "Drizzle",
"description": "Drizzle lets you build your project the way you want, without interfering with your project or structure. Using Drizzle you can define and manage database schemas in TypeScript, access your data in a SQL-like or relational way, and take advantage of opt-in tools to make your developer experience amazing.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Drizzle Github",
"url": "https://github.com/drizzle-team/drizzle-orm",
"type": "opensource"
},
{
"title": "Drizzle Website",
"url": "https://orm.drizzle.team/",
@@ -1631,6 +1626,11 @@
"url": "https://orm.drizzle.team/docs/overview",
"type": "article"
},
{
"title": "Drizzle Github",
"url": "https://github.com/drizzle-team/drizzle-orm",
"type": "article"
},
{
"title": "Getting Started with Drizzle",
"url": "https://dev.to/franciscomendes10866/getting-started-with-drizzle-orm-a-beginners-tutorial-4782",

View File

@@ -2,13 +2,7 @@
"lDIy56RyC1XM7IfORsSLD": {
"title": "Introduction",
"description": "PostgreSQL is a powerful, open-source Object-Relational Database Management System (ORDBMS) that is known for its robustness, extensibility, and SQL compliance. It was initially developed at the University of California, Berkeley, in the 1980s and has since become one of the most popular open-source databases in the world.",
"links": [
{
"title": "History of POSTGRES to PostgreSQL",
"url": "https://www.postgresql.org/docs/current/history.html",
"type": "article"
}
]
"links": []
},
"soar-NBWCr4xVKj7ttfnc": {
"title": "What are Relational Databases?",
@@ -104,6 +98,11 @@
"title": "Data Types",
"description": "PostgreSQL offers a rich and diverse set of data types, catering to a wide range of applications and ensuring data integrity and performance. These include standard numeric types such as integers, floating-point numbers, and serial types for auto-incrementing fields. Character types like VARCHAR and TEXT handle varying lengths of text, while DATE, TIME, and TIMESTAMP support a variety of temporal data requirements. PostgreSQL also supports a comprehensive set of Boolean, enumerated (ENUM), and composite types, enabling more complex data structures. Additionally, it excels with its support for JSON and JSONB data types, allowing for efficient storage and querying of semi-structured data. The inclusion of array types, geometric data types, and the PostGIS extension for geographic data further extends PostgreSQL's versatility, making it a powerful tool for a broad spectrum of data management needs.\n\nLearn more from the following resources:",
"links": [
{
"title": "",
"url": "https://www.instaclustr.com/blog/postgresql-data-types-mappings-to-sql-jdbc-and-java-data-types/",
"type": "article"
},
{
"title": "Data Types",
"url": "https://www.postgresql.org/docs/current/datatype.html",
@@ -113,11 +112,6 @@
"title": "An introduction to PostgreSQL data types",
"url": "https://www.prisma.io/dataguide/postgresql/introduction-to-data-types",
"type": "article"
},
{
"title": "PostgreSQL® Data Types: Mappings to SQL, JDBC, and Java Data Types",
"url": "https://www.instaclustr.com/blog/postgresql-data-types-mappings-to-sql-jdbc-and-java-data-types/",
"type": "article"
}
]
},
@@ -312,12 +306,7 @@
"description": "Multi-Version Concurrency Control (MVCC) is a technique used by PostgreSQL to allow multiple transactions to access the same data concurrently without conflicts or delays. It ensures that each transaction has a consistent snapshot of the database and can operate on its own version of the data.\n\nLearn more from the following resources:",
"links": [
{
"title": "Intro to MVCC",
"url": "https://www.postgresql.org/docs/current/mvcc-intro.html",
"type": "article"
},
{
"title": "Multiversion concurrency control - Wikipedia",
"title": "",
"url": "https://en.wikipedia.org/wiki/Multiversion_concurrency_control",
"type": "article"
},
@@ -769,11 +758,6 @@
"title": "PostgreSQL INTERSECT Operator",
"url": "https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-intersect/",
"type": "article"
},
{
"title": "PostgreSQL EXCEPT Operator",
"url": "https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-except/",
"type": "article"
}
]
},

View File

@@ -95,17 +95,6 @@
"title": "Type Conversion and Casting",
"url": "https://www.programiz.com/python-programming/type-conversion-and-casting",
"type": "article"
}
]
},
"fNTb9y3zs1HPYclAmu_Wv": {
"title": "Exceptions",
"description": "Python exceptions are events that occur during the execution of a program and disrupt the normal flow of the program's instructions. When an exception is raised, it indicates that an error has occurred. Python provides a way to handle these exceptions using try-except blocks, allowing developers to manage errors gracefully and ensure the program can continue or exit smoothly.",
"links": [
{
"title": "Exceptions Documentation",
"url": "https://docs.python.org/3/tutorial/errors.html#exceptions",
"type": "article"
},
{
"title": "Python Exceptions: An Introduction",
@@ -126,6 +115,17 @@
"title": "Python Try Except",
"url": "https://www.w3schools.com/python/python_try_except.asp",
"type": "article"
}
]
},
"fNTb9y3zs1HPYclAmu_Wv": {
"title": "Exceptions",
"description": "Python exceptions are events that occur during the execution of a program and disrupt the normal flow of the program's instructions. When an exception is raised, it indicates that an error has occurred. Python provides a way to handle these exceptions using try-except blocks, allowing developers to manage errors gracefully and ensure the program can continue or exit smoothly.",
"links": [
{
"title": "Exceptions Documentation",
"url": "https://docs.python.org/3/tutorial/errors.html#exceptions",
"type": "article"
},
{
"title": "Exception Handling in Python",
@@ -227,16 +227,6 @@
"title": "Loops in Python",
"url": "https://www.geeksforgeeks.org/loops-in-python/",
"type": "article"
},
{
"title": "Python \"while\" Loops (Indefinite Iteration)",
"url": "https://realpython.com/python-while-loop/",
"type": "article"
},
{
"title": "Python \"for\" Loops (Definite Iteration)",
"url": "https://realpython.com/python-for-loop/#the-guts-of-the-python-for-loop",
"type": "article"
}
]
},
@@ -336,6 +326,11 @@
"url": "https://www.programiz.com/dsa",
"type": "article"
},
{
"title": "DSA Course by Google",
"url": "https://www.udacity.com/course/data-structures-and-algorithms-in-python--ud513",
"type": "article"
},
{
"title": "Explore top posts about Algorithms",
"url": "https://app.daily.dev/tags/algorithms?ref=roadmapsh",
@@ -1212,16 +1207,10 @@
}
]
},
"DS6nuAUhUYcqiJDmQisKM": {
"black@DS6nuAUhUYcqiJDmQisKM.md": {
"title": "black",
"description": "black is a code formatter for Python. It is a tool that automatically formats Python code to adhere to the PEP 8 style guide. It is a great tool to use in your Python projects to ensure that your code is formatted consistently and correctly.",
"links": [
{
"title": "black documentation",
"url": "https://black.readthedocs.io/en/stable/",
"type": "article"
}
]
"description": "",
"links": []
},
"tsh_vbhzKz1-H9Vh69tsK": {
"title": "yapf",

View File

@@ -586,7 +586,7 @@
},
"w3bNp7OkehI1gjx8NzlC8": {
"title": "useMemo",
"description": "`useMemo` is a React hook that memoizes the result of a function. It is used to optimize performance by caching the result of a function and returning the cached result when the inputs to the function have not changed.\n\nLearn more from the following resources:",
"description": "`useMemo` is a React hook that memorizes the result of a function. It is used to optimize performance by caching the result of a function and returning the cached result when the inputs to the function have not changed.\n\nLearn more from the following resources:",
"links": [
{
"title": "useMemo Docs",
@@ -1618,26 +1618,21 @@
},
"ElgRwv5LSVg5FXGx-2K2s": {
"title": "TypeScript",
"description": "TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.\n\nVisit the following resources to learn more:",
"description": "TypeScript is a free and open-source high-level programming language developed by Microsoft that adds static typing with optional type annotations to JavaScript. It is designed for the development of large applications and transpiles to JavaScript. Because TypeScript is a superset of JavaScript, all JavaScript programs are syntactically valid TypeScript, but they can fail to type-check for safety reasons.\n\nTypeScript supports definition files that can contain type information of existing JavaScript libraries, much like C++ header files can describe the structure of existing object files. This enables other programs to use the values defined in the files as if they were statically typed TypeScript entities. There are third-party header files for popular libraries such as jQuery, MongoDB, and D3.js. TypeScript headers for the Node.js library modules are also available, allowing development of Node.js programs within TypeScript.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Official Website",
"url": "https://www.typescriptlang.org/",
"title": "TypeScript Website",
"url": "https://typescriptlang.org/",
"type": "article"
},
{
"title": "The TypeScript Handbook",
"url": "https://www.typescriptlang.org/docs/handbook/intro.html",
"title": "TypeScript Docs",
"url": "https://typescriptlang.org/docs",
"type": "article"
},
{
"title": "Explore top posts about TypeScript",
"url": "https://app.daily.dev/tags/typescript?ref=roadmapsh",
"type": "article"
},
{
"title": "TypeScript for Beginners",
"url": "https://www.youtube.com/watch?v=BwuLxPH8IDs",
"title": "Learn Typescript Basics in 12 Minutes",
"url": "https://www.youtube.com/watch?v=ahCwqrYpIuM&pp=ygULdHlwZXNjcnJpcHQ%3D",
"type": "video"
}
]

View File

@@ -315,6 +315,11 @@
"url": "https://www.tutorialspoint.com/typescript/index.htm",
"type": "article"
},
{
"title": "Scrimba — TypeScript Basics",
"url": "https://scrimba.com/learn/typescript?via=roadmap",
"type": "article"
},
{
"title": "Explore top posts about JavaScript",
"url": "https://app.daily.dev/tags/javascript?ref=roadmapsh",
@@ -1155,21 +1160,10 @@
"description": "",
"links": []
},
"6FDGecsHbqY-cm32yTZJa": {
"functional-programming@6FDGecsHbqY-cm32yTZJa.md": {
"title": "Functional Programming",
"description": "Functional programming is a programming paradigm designed to handle pure mathematical functions. This paradigm is totally focused on writing more compounded and pure functions.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Functional Programming with JavaScript",
"url": "https://www.telerik.com/blogs/functional-programming-javascript",
"type": "article"
},
{
"title": "Learning Functional Programming",
"url": "https://youtube.com/watch?v=e-5obm1G_FY",
"type": "video"
}
]
"description": "",
"links": []
},
"mCiYCbKIOVU34qil_q7Hg": {
"title": "React, Vue, Angular",
@@ -1427,71 +1421,18 @@
},
"O7H6dt3Z7EKohxfJzwbPM": {
"title": "Kanban",
"description": "`Kanban` is a popular agile methodology that focuses on visualizing workflow and continuously improving that flow. It's a more flexible approach than Scrum, without the rigid framework.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What Is Kanban? A Simple Guide to Improve Efficiency.",
"url": "https://businessmap.io/kanban-resources/getting-started/what-is-kanban",
"type": "article"
},
{
"title": "Kanban Methodology: The Simplest Agile Framework ",
"url": "https://kissflow.com/project/agile/kanban-methodology/",
"type": "article"
},
{
"title": "What is Kanban Methodology? The Ultimate Guide",
"url": "https://www.wrike.com/kanban-guide/what-is-kanban/",
"type": "article"
}
]
"description": "",
"links": []
},
"PKqwKvoffm0unwcFwpojk": {
"title": "Scrum",
"description": "`Scrum` is a popular agile framework used for project management, particularly in software development. It emphasizes iterative development, collaboration, and flexibility to deliver high-quality products.\n\nKey elements of Scrum:\n\n* **Sprints**: Time-boxed iterations (usually 2-4 weeks) where teams work on specific goals.\n* **Product Backlog**: Prioritized list of features or requirements for the product.\n* **Sprint Backlog**: Selected items from the Product Backlog to be completed during a Sprint.\n* **Daily Scrum (Stand-up)**: Brief daily meeting where team members share progress, challenges, and plans for the day.\n* **Sprint Review**: Meeting at the end of a Sprint to demonstrate completed work and gather feedback.\n* **Sprint Retrospective**: Meeting to reflect on the Sprint, identify improvements, and adjust processes for the next Sprint.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is scrum and how to get started",
"url": "https://www.atlassian.com/agile/scrum.",
"type": "article"
},
{
"title": "Scrum Methodology: The Complete Guide & Best Practices",
"url": "https://thedigitalprojectmanager.com/projects/pm-methodology/scrum-methodology-complete-guide/",
"type": "article"
},
{
"title": "Essential Topics for the Scrum Product Owner",
"url": "https://www.scrum.org/resources/blog/essential-topics-scrum-product-owner",
"type": "article"
},
{
"title": "Scrum • Topics - Thriving Technologist",
"url": "https://thrivingtechnologist.com/topics/scrum/",
"type": "article"
}
]
"description": "",
"links": []
},
"7fL9lSu4BD1wRjnZy9tM9": {
"title": "XP",
"description": "`Extreme Programming (XP)` is a popular agile software development framework that emphasizes speed, simplicity, and quality. It was developed by Kent Beck in the late 1990s and is based on five values:\n\n* **Communication**: Open and honest communication among team members and stakeholders is essential.\n* **Simplicity**: The simplest solution that works is always preferred.\n* **Feedback**: Continuous feedback from customers and team members is used to improve the product.\n* **Courage**: Team members must be willing to make changes and take risks.\n* **Respect**: Everyone on the team is treated with respect.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is Extreme Programming (XP)?",
"url": "https://www.agilealliance.org/glossary/xp/",
"type": "article"
},
{
"title": "It's Values, Principles, And Practices",
"url": "https://www.nimblework.com/agile/extreme-programming-xp/",
"type": "article"
},
{
"title": "Extreme Programming (XP)",
"url": "https://scrum-master.org/en/extreme-programming-xp-a-beginners-guide-to-the-agile-method/",
"type": "article"
}
]
"description": "",
"links": []
},
"cBWJ6Duw99tSKr7U6OW3A": {
"title": "Networks",
@@ -1511,24 +1452,8 @@
},
"UCCT7-E_QUKPg3jAsjobx": {
"title": "TCP/IP Model",
"description": "The `TCP/IP model` defines how devices should transmit data between them and enables communication over networks and large distances. The model represents how data is exchanged and organized over networks. It is split into four layers, which set the standards for data exchange and represent how data is handled and packaged when being delivered between applications, devices, and servers.\n\n* **Network Access Layer**: The network access layer is a group of applications requiring network communications. This layer is responsible for generating the data and requesting connections.\n \n* **Internet Layer**: The internet layer is responsible for sending packets from a network and controlling their movement across a network to ensure they reach their destination.\n \n* **Transport Layer**: The transport layer is responsible for providing a solid and reliable data connection between the original application or device and its intended destination.\n \n* **Application Layer**: The application layer refers to programs that need TCP/IP to help them communicate with each other.\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is Transmission Control Protocol TCP/IP? - Fortinet",
"url": "https://www.fortinet.com/resources/cyberglossary/tcp-ip#:~:text=The%20TCP%2FIP%20model%20defines,exchanged%20and%20organized%20over%20networks.",
"type": "article"
},
{
"title": "TCP/IP Model",
"url": "https://www.geeksforgeeks.org/tcp-ip-model/",
"type": "article"
},
{
"title": "What is TCP/IP and How Does it Work?",
"url": "https://www.techtarget.com/searchnetworking/definition/TCP-IP",
"type": "article"
}
]
"description": "",
"links": []
},
"Nq6o6Ty8VyNRsvg-UWp7D": {
"title": "HTTP, HTTPS",

File diff suppressed because it is too large Load Diff

View File

@@ -352,7 +352,7 @@
"description": "The lifecycle meta-argument in Terraform customizes the behavior of resources during creation, update, and deletion. It includes settings such as create\\_before\\_destroy, which ensures a new resource is created before the old one is destroyed, preventing downtime. prevent\\_destroy protects resources from accidental deletion, and ignore\\_changes specifies attributes to ignore during updates, allowing external modifications without triggering Terraform changes. These options provide fine-grained control over resource management, ensuring that the desired state of infrastructure is maintained with minimal disruption and precise handling of resource lifecycles.\n\nLearn more from the following resources:",
"links": [
{
"title": "Terraform Docs - lifecycle",
"title": "Terraform Docs - for_each",
"url": "https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle",
"type": "article"
},
@@ -449,14 +449,8 @@
},
"fm8oUyNvfdGWTgLsYANUr": {
"title": "Environment Variables",
"description": "Environment variables can be used to customize various aspects of Terraform. You can set these variables to change the default behaviour of terraform such as increase verbosity, update log file path, set workspace, etc. Envrionment variables are optional and terraform does not need them by default.\n\nLearn more from the following resources:",
"links": [
{
"title": "Environment Variables",
"url": "https://developer.hashicorp.com/terraform/cli/config/environment-variables",
"type": "article"
}
]
"description": "",
"links": []
},
"rdphcVd-Vq972y4H8CxIj": {
"title": "Variable Definition File",
@@ -476,30 +470,13 @@
},
"U2n2BtyUrOFLnw9SZYV_w": {
"title": "Validation Rules",
"description": "Validation rules can be used to specify custom validations to a variable. The motive of adding validation rules is to make the variable comply with the rules. The validation rules can be added using a `validation` block.\n\nLearn more from the following resources:",
"links": [
{
"title": "Custom Validation Rules",
"url": "https://developer.hashicorp.com/terraform/language/values/variables#custom-validation-rules",
"type": "article"
}
]
"description": "",
"links": []
},
"1mFih8uFs3Lc-1PLgwiAU": {
"title": "Local Values",
"description": "Local values can be understood as a name assigned to any expression to use it multiple times directly by the name in your terraform module. Local values are referred to as locals and can be declared using the `locals` block. Local values can be a literal constants, resource attributes, variables, or other local values. Local values are helpful to define expressions or values that you need to use multiple times in the module as it allows the value to be updated easily just by updating the local value. A local value can be accessed using the `local` argument like `local.<value_name>`.\n\nLearn more from the following resources:",
"links": [
{
"title": "Local Values",
"url": "https://developer.hashicorp.com/terraform/language/values/locals",
"type": "article"
},
{
"title": "@Article@Terraform Locals",
"url": "https://spacelift.io/blog/terraform-locals",
"type": "article"
}
]
"description": "",
"links": []
},
"7GK4fQf1FRKrZgZkxNahj": {
"title": "Outputs",
@@ -558,7 +535,7 @@
"type": "article"
},
{
"title": "Suppressing values in CLI output",
"title": "Surpressing values in CLI output",
"url": "https://developer.hashicorp.com/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output",
"type": "article"
}

View File

@@ -9,7 +9,7 @@
"type": "article"
},
{
"title": "TypeScript Official Handbook",
"title": "TypeScript Handbook",
"url": "https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html",
"type": "article"
},
@@ -163,7 +163,7 @@
"description": "The TypeScript Playground is a great tool to learn TypeScript. It allows you to write TypeScript code and see the JavaScript output. It also allows you to share your code with others.\n\nLearn more from the following links:",
"links": [
{
"title": "TypeScript Official - Playground",
"title": "TypeScript - Playground",
"url": "https://www.typescriptlang.org/play",
"type": "article"
},
@@ -349,7 +349,7 @@
"description": "The `never` type represents the type of values that never occur. For instance, `never` is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be `true`.\n\nThe never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, `never` (except `never` itself). Even any isnt assignable to `never`.\n\nExamples of functions returning never:\n\n // Function returning never must not have a reachable end point\n function error(message: string): never {\n throw new Error(message);\n }\n \n // Inferred return type is never\n function fail() {\n return error('Something failed');\n }\n \n // Function returning never must not have a reachable end point\n function infiniteLoop(): never {\n while (true) {}\n }\n \n\nLearn more from the following links:",
"links": [
{
"title": "Never Type",
"title": "Never",
"url": "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type",
"type": "article"
}
@@ -379,14 +379,8 @@
},
"afTNr36VqeXoJpHxm2IoS": {
"title": "as any",
"description": "`any` is a special type in TypeScript that represents a value of any type. When a value is declared with the any type, the compiler will not perform any type checks or type inference on that value.\n\nFor example:\n\n let anyValue: any = 42;\n \n // we can assign any value to anyValue, regardless of its type\n anyValue = 'Hello, world!';\n anyValue = true;\n \n\nLearn more from the following links:",
"links": [
{
"title": "Arrays",
"url": "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any",
"type": "article"
}
]
"description": "`any` is a special type in TypeScript that represents a value of any type. When a value is declared with the any type, the compiler will not perform any type checks or type inference on that value.\n\nFor example:\n\n let anyValue: any = 42;\n \n // we can assign any value to anyValue, regardless of its type\n anyValue = 'Hello, world!';\n anyValue = true;",
"links": []
},
"mjaL5ocLnM8VQlhUxW6KU": {
"title": "Non-null Assertion",
@@ -404,7 +398,7 @@
"description": "TypeScript developers are often faced with a dilemma: we want to ensure that some expression matches some type, but also want to keep the most specific type of that expression for inference purposes.\n\nFor example:\n\n // Each property can be a string or an RGB tuple.\n const palette = {\n red: [255, 0, 0],\n green: '#00ff00',\n bleu: [0, 0, 255],\n // ^^^^ sacrebleu - we've made a typo!\n };\n \n // We want to be able to use array methods on 'red'...\n const redComponent = palette.red.at(0);\n \n // or string methods on 'green'...\n const greenNormalized = palette.green.toUpperCase();\n \n\nNotice that weve written `bleu`, whereas we probably should have written `blue`. We could try to catch that `bleu` typo by using a type annotation on palette, but wed lose the information about each property.\n\n type Colors = 'red' | 'green' | 'blue';\n type RGB = [red: number, green: number, blue: number];\n \n const palette: Record<Colors, string | RGB> = {\n red: [255, 0, 0],\n green: '#00ff00',\n bleu: [0, 0, 255],\n // ~~~~ The typo is now correctly detected\n };\n // But we now have an undesirable error here - 'palette.red' \"could\" be a string.\n const redComponent = palette.red.at(0);\n \n\nThe `satisfies` operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression. As an example, we could use `satisfies` to validate that all the properties of palette are compatible with `string | number[]`:\n\n type Colors = 'red' | 'green' | 'blue';\n type RGB = [red: number, green: number, blue: number];\n \n const palette = {\n red: [255, 0, 0],\n green: '#00ff00',\n bleu: [0, 0, 255],\n // ~~~~ The typo is now caught!\n } satisfies Record<Colors, string | RGB>;\n \n // Both of these methods are still accessible!\n const redComponent = palette.red.at(0);\n const greenNormalized = palette.green.toUpperCase();\n \n\nLearn more from the following resources:",
"links": [
{
"title": "satisfies Keyword",
"title": "Satisfies Keyword",
"url": "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator",
"type": "article"
}
@@ -434,7 +428,7 @@
},
"qefnsugcveizVq2TORRgn": {
"title": "Combining Types",
"description": "In TypeScript, you can combine types using type union and type intersection.\n\nType Union\n----------\n\nThe union operator `|` is used to combine two or more types into a single type that represents all the possible types. For example:\n\n type stringOrNumber = string | number;\n let value: stringOrNumber = 'hello';\n \n value = 42;\n \n\nType Intersection\n-----------------\n\nThe intersection operator `&` is used to intersect two or more types into a single type that represents the properties of all the types. For example:\n\n interface A {\n a: string;\n }\n \n interface B {\n b: number;\n }\n \n type AB = A & B;\n let value: AB = { a: 'hello', b: 42 };\n \n\nLearn more from the following links:",
"description": "In TypeScript, you can combine types using type union and type intersection.\n\n### Type Union:\n\nThe union operator `|` is used to combine two or more types into a single type that represents all the possible types. For example:\n\n type stringOrNumber = string | number;\n let value: stringOrNumber = 'hello';\n \n value = 42;\n \n\n### Type Intersection:\n\nThe intersection operator `&` is used to intersect two or more types into a single type that represents the properties of all the types. For example:\n\n interface A {\n a: string;\n }\n \n interface B {\n b: number;\n }\n \n type AB = A & B;\n let value: AB = { a: 'hello', b: 42 };\n \n\nLearn more from the following links:",
"links": [
{
"title": "Union Types in TypeScript",
@@ -496,7 +490,7 @@
"description": "The `keyof` operator in TypeScript is used to get the union of keys from an object type. Here's an example of how it can be used:\n\n interface User {\n name: string;\n age: number;\n location: string;\n }\n \n type UserKeys = keyof User; // \"name\" | \"age\" | \"location\"\n const key: UserKeys = 'name';\n \n\nIn this example, `UserKeys` is a type that represents the union of keys from the `User` interface, which is `\"name\"` | `\"age\"` | `\"location\"`. And a constant named `key` with the type `UserKeys` is declared with the value `\"name\"`.\n\nLearn more from the following links:",
"links": [
{
"title": "keyof Type Operator",
"title": "Keyof Type Operator",
"url": "https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#handbook-content",
"type": "article"
}
@@ -652,14 +646,8 @@
},
"lvtTSHH9yBTCiLng8btnI": {
"title": "Hybrid Types",
"description": "In TypeScript, a hybrid type is a type that combines multiple types into a single type. The resulting type is considered a union of those types. This allows you to specify that a value can have multiple types, rather than just one.\n\nFor example, you can create a hybrid type that can accept either a string or a number:\n\n type StringOrNumber = string | number;\n \n\nYou can also use hybrid types to create more complex types that can represent a combination of several different types of values. For example:\n\n type Education = {\n degree: string;\n school: string;\n year: number;\n };\n \n type User = {\n name: string;\n age: number;\n email: string;\n education: Education;\n };\n \n\nLearn more from the following links:",
"links": [
{
"title": "Geeksforgeeks.org - Hybrid Types",
"url": "https://www.geeksforgeeks.org/what-are-hybrid-types-in-typescript/#:~:text=Hybrid%20types%20are%20a%20combination,properties%20like%20a%20regular%20object.",
"type": "article"
}
]
"description": "In TypeScript, a hybrid type is a type that combines multiple types into a single type. The resulting type is considered a union of those types. This allows you to specify that a value can have multiple types, rather than just one.\n\nFor example, you can create a hybrid type that can accept either a string or a number:\n\n type StringOrNumber = string | number;\n \n\nYou can also use hybrid types to create more complex types that can represent a combination of several different types of values. For example:\n\n type Education = {\n degree: string;\n school: string;\n year: number;\n };\n \n type User = {\n name: string;\n age: number;\n email: string;\n education: Education;\n };",
"links": []
},
"ib0jfZzukYOZ42AdJqt_W": {
"title": "Classes",
@@ -720,11 +708,6 @@
"title": "Inheritance vs Polymorphism",
"description": "Inheritance and polymorphism are two fundamental concepts in object-oriented programming, and they are supported in TypeScript as well.\n\nInheritance refers to a mechanism where a subclass inherits properties and methods from its parent class. This allows a subclass to reuse the code and behavior of its parent class while also adding or modifying its own behavior. In TypeScript, inheritance is achieved using the extends keyword.\n\nPolymorphism refers to the ability of an object to take on many forms. This allows objects of different classes to be treated as objects of a common class, as long as they share a common interface or inheritance hierarchy. In TypeScript, polymorphism is achieved through method overriding and method overloading.\n\n class Animal {\n makeSound(): void {\n console.log('Making animal sound');\n }\n }\n \n class Dog extends Animal {\n makeSound(): void {\n console.log('Bark');\n }\n }\n \n class Cat extends Animal {\n makeSound(): void {\n console.log('Meow');\n }\n }\n \n let animal: Animal;\n \n animal = new Dog();\n animal.makeSound(); // Output: Bark\n \n animal = new Cat();\n animal.makeSound(); // Output: Meow\n \n\nLearn more from the following resources:",
"links": [
{
"title": "Dev.to - Mastering OOP in TypeScript",
"url": "https://dev.to/rajrathod/mastering-object-oriented-programming-with-typescript-encapsulation-abstraction-inheritance-and-polymorphism-explained-c6p",
"type": "article"
},
{
"title": "Inheritance and Polymorphism In TypeScript",
"url": "https://www.youtube.com/watch?v=Sn6K57YSuwU",
@@ -810,7 +793,7 @@
},
"gBTem9Dp3IQLAkqGX4fOF": {
"title": "Partial",
"description": "The Partial type in TypeScript allows you to make all properties of a type optional. This is useful when you need to create an object with only a subset of the properties of an existing type.\n\nHere's an example of using the Partial type in TypeScript:\n\n interface User {\n name: string;\n age: number;\n email: string;\n }\n \n function createUser(user: Partial<User>): User {\n return {\n name: 'John Doe',\n age: 30,\n email: 'john.doe@example.com',\n ...user,\n };\n }\n \n const newUser = createUser({ name: 'Jane Doe' });\n \n console.log(newUser);\n // Output: { name: 'Jane Doe', age: 30, email: 'john.doe@example.com' }\n \n\nLearn more from the following links:\n\n@official@Partial",
"description": "The Partial type in TypeScript allows you to make all properties of a type optional. This is useful when you need to create an object with only a subset of the properties of an existing type.\n\nHere's an example of using the Partial type in TypeScript:\n\n interface User {\n name: string;\n age: number;\n email: string;\n }\n \n function createUser(user: Partial<User>): User {\n return {\n name: 'John Doe',\n age: 30,\n email: 'john.doe@example.com',\n ...user,\n };\n }\n \n const newUser = createUser({ name: 'Jane Doe' });\n \n console.log(newUser);\n // Output: { name: 'Jane Doe', age: 30, email: 'john.doe@example.com' }\n \n\nLearn more from the following links:\n\n@article@Partial",
"links": []
},
"E88tHQvARkHURZwGaO02l": {
@@ -837,7 +820,7 @@
},
"IuO9-O_DQdDYuAbdGWdgb": {
"title": "Readonly",
"description": "Readonly constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.\n\n interface Todo {\n title: string;\n }\n \n const todo: Readonly<Todo> = {\n title: 'Delete inactive users',\n };\n \n // Cannot assign to 'title' because it is a read-only property.\n todo.title = 'Hello';\n \n\nLearn more from the following links:\n\n@official@Readonly",
"description": "Readonly constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.\n\n interface Todo {\n title: string;\n }\n \n const todo: Readonly<Todo> = {\n title: 'Delete inactive users',\n };\n \n // Cannot assign to 'title' because it is a read-only property.\n todo.title = 'Hello';\n \n\nLearn more from the following links:\n\n@article@Readonly",
"links": []
},
"DRdBmF5Dt_r09LoPOxOuq": {
@@ -875,40 +858,28 @@
},
"_BAZlBEzE7ddr315OeHvl": {
"title": "NonNullable",
"description": "Non-Nullable constructs a type by excluding `null` and `undefined` from Type.\n\n type T0 = NonNullable<string | number | undefined>;\n // type T0 = string | number\n \n type T1 = NonNullable<string[] | null | undefined>;\n // type T1 = string[]\n \n\nLearn more from the following links:\n\n@official@NonNullable",
"description": "Non-Nullable constructs a type by excluding `null` and `undefined` from Type.\n\n type T0 = NonNullable<string | number | undefined>;\n // type T0 = string | number\n \n type T1 = NonNullable<string[] | null | undefined>;\n // type T1 = string[]\n \n\nLearn more from the following links:\n\n@article@NonNullable",
"links": []
},
"a7hl0iMZ-jcUACxqIYVqv": {
"title": "Parameters",
"description": "Parameters constructs a tuple type from the types used in the parameters of a function type Type.\n\n type T0 = Parameters<() => string>;\n // type T0 = []\n \n type T1 = Parameters<(s: string) => void>;\n // type T1 = [s: string]\n \n type T2 = Parameters<<T>(arg: T) => T>;\n // type T2 = [arg: unknown]\n \n declare function f1(arg: { a: number; b: string }): void;\n type T3 = Parameters<typeof f1>;\n // type T3 = [arg: {\n // a: number;\n // b: string;\n // }]\n \n type T4 = Parameters<any>;\n // type T4 = unknown[]\n \n type T5 = Parameters<never>;\n // type T5 = never\n \n type T6 = Parameters<string>;\n // ^ Type 'string' does not satisfy the constraint '(...args: any) => any'.\n \n type T7 = Parameters<Function>;\n // ^ Type 'Function' does not satisfy the constraint '(...args: any) => any'.\n \n\nLearn more from the following links:\n\n@official@Parameters",
"description": "Parameters constructs a tuple type from the types used in the parameters of a function type Type.\n\n type T0 = Parameters<() => string>;\n // type T0 = []\n \n type T1 = Parameters<(s: string) => void>;\n // type T1 = [s: string]\n \n type T2 = Parameters<<T>(arg: T) => T>;\n // type T2 = [arg: unknown]\n \n declare function f1(arg: { a: number; b: string }): void;\n type T3 = Parameters<typeof f1>;\n // type T3 = [arg: {\n // a: number;\n // b: string;\n // }]\n \n type T4 = Parameters<any>;\n // type T4 = unknown[]\n \n type T5 = Parameters<never>;\n // type T5 = never\n \n type T6 = Parameters<string>;\n // ^ Type 'string' does not satisfy the constraint '(...args: any) => any'.\n \n type T7 = Parameters<Function>;\n // ^ Type 'Function' does not satisfy the constraint '(...args: any) => any'.\n \n\nLearn more from the following links:\n\n@article@Parameters",
"links": []
},
"On75JR_UkiIlha0_qaSeu": {
"title": "ReturnType",
"description": "Return type constructs a type consisting of the return type of function Type.\n\n type T0 = ReturnType<() => string>;\n // type T0 = string\n \n type T1 = ReturnType<(s: string) => void>;\n // type T1 = void\n \n type T2 = ReturnType<<T>() => T>;\n // type T2 = unknown\n \n type T3 = ReturnType<<T extends U, U extends number[]>() => T>;\n // type T3 = number[]\n \n declare function f1(): { a: number; b: string };\n type T4 = ReturnType<typeof f1>;\n // type T4 = {\n // a: number;\n // b: string;\n // }\n \n type T5 = ReturnType<any>;\n // type T5 = any\n \n type T6 = ReturnType<never>;\n // type T6 = never\n \n type T7 = ReturnType<string>;\n // ^ Type 'string' does not satisfy the constraint '(...args: any) => any'.\n \n type T8 = ReturnType<Function>;\n // ^ Type 'Function' does not satisfy the constraint '(...args: any) => any'.\n \n\nLearn more from the following links:\n\n@official@ReturnType",
"description": "Return type constructs a type consisting of the return type of function Type.\n\n type T0 = ReturnType<() => string>;\n // type T0 = string\n \n type T1 = ReturnType<(s: string) => void>;\n // type T1 = void\n \n type T2 = ReturnType<<T>() => T>;\n // type T2 = unknown\n \n type T3 = ReturnType<<T extends U, U extends number[]>() => T>;\n // type T3 = number[]\n \n declare function f1(): { a: number; b: string };\n type T4 = ReturnType<typeof f1>;\n // type T4 = {\n // a: number;\n // b: string;\n // }\n \n type T5 = ReturnType<any>;\n // type T5 = any\n \n type T6 = ReturnType<never>;\n // type T6 = never\n \n type T7 = ReturnType<string>;\n // ^ Type 'string' does not satisfy the constraint '(...args: any) => any'.\n \n type T8 = ReturnType<Function>;\n // ^ Type 'Function' does not satisfy the constraint '(...args: any) => any'.\n \n\nLearn more from the following links:\n\n@article@ReturnType",
"links": []
},
"izGAjNtrh3BzQt3KiZX0W": {
"title": "InstanceType",
"description": "This type constructs a type consisting of the instance type of a constructor function in Type.\n\n class C {\n x = 0;\n y = 0;\n }\n \n type T0 = InstanceType<typeof C>;\n // type T0 = C\n \n type T1 = InstanceType<any>;\n // type T1 = any\n \n type T2 = InstanceType<never>;\n // type T2 = never\n \n type T3 = InstanceType<string>;\n // ^ Type 'string' does not satisfy the constraint 'abstract new (...args: any) => any'.\n \n type T4 = InstanceType<Function>;\n // ^ Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'.\n \n\nLearn more from the following links:",
"links": [
{
"title": "InstanceType<Type>",
"url": "https://www.typescriptlang.org/docs/handbook/utility-types.html#instancetypetype",
"type": "article"
}
]
"description": "This type constructs a type consisting of the instance type of a constructor function in Type.\n\n class C {\n x = 0;\n y = 0;\n }\n \n type T0 = InstanceType<typeof C>;\n // type T0 = C\n \n type T1 = InstanceType<any>;\n // type T1 = any\n \n type T2 = InstanceType<never>;\n // type T2 = never\n \n type T3 = InstanceType<string>;\n // ^ Type 'string' does not satisfy the constraint 'abstract new (...args: any) => any'.\n \n type T4 = InstanceType<Function>;\n // ^ Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'.\n \n\nLearn more from the following links:\n\n@article@InstanceType",
"links": []
},
"aEhI_9mFWXRIZh1ZxTuzu": {
"title": "Awaited",
"description": "This type is meant to model operations like await in async functions, or the `.then()` method on Promises - specifically, the way that they recursively unwrap Promises.\n\n type A = Awaited<Promise<string>>;\n // type A = string\n \n type B = Awaited<Promise<Promise<number>>>;\n // type B = number\n \n type C = Awaited<boolean | Promise<number>>;\n // type C = number | boolean\n \n\nLearn more from the following links:",
"links": [
{
"title": "Awaited<Type>",
"url": "https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype",
"type": "article"
}
]
"description": "This type is meant to model operations like await in async functions, or the `.then()` method on Promises - specifically, the way that they recursively unwrap Promises.\n\n type A = Awaited<Promise<string>>;\n // type A = string\n \n type B = Awaited<Promise<Promise<number>>>;\n // type B = number\n \n type C = Awaited<boolean | Promise<number>>;\n // type C = number | boolean\n \n\nLearn more from the following links:\n\n@article@Awaited",
"links": []
},
"2F7vOL__v9dLBohA263aj": {
"title": "Advanced Types",
@@ -977,14 +948,8 @@
},
"N8xBTJ74xv1E5hSLYZtze": {
"title": "Recursive Types",
"description": "Recursive types in TypeScript are a way to define a type that references itself. Recursive types are used to define complex data structures, such as trees or linked lists, where a value can contain one or more values of the same type.\n\nFor example, the following is a recursive type that represents a linked list:\n\n type LinkedList<T> = {\n value: T;\n next: LinkedList<T> | null;\n };\n \n let list: LinkedList<number> = {\n value: 1,\n next: { value: 2, next: { value: 3, next: null } },\n };\n \n\nIn this example, the `LinkedList` type is defined as a type that extends `T` and contains a property `next` of the same type `LinkedList<T>`. This allows us to create a linked list where each node contains a value of type `T` and a reference to the next node in the list.\n\nLearn more from the following links:",
"links": [
{
"title": "Recursive Types in TypeScript",
"url": "https://www.typescriptlang.org/play/3-7/types-and-code-flow/recursive-type-references.ts.html",
"type": "article"
}
]
"description": "Recursive types in TypeScript are a way to define a type that references itself. Recursive types are used to define complex data structures, such as trees or linked lists, where a value can contain one or more values of the same type.\n\nFor example, the following is a recursive type that represents a linked list:\n\n type LinkedList<T> = {\n value: T;\n next: LinkedList<T> | null;\n };\n \n let list: LinkedList<number> = {\n value: 1,\n next: { value: 2, next: { value: 3, next: null } },\n };\n \n\nIn this example, the `LinkedList` type is defined as a type that extends `T` and contains a property `next` of the same type `LinkedList<T>`. This allows us to create a linked list where each node contains a value of type `T` and a reference to the next node in the list.",
"links": []
},
"sE9lqkkqwnsVJxTJv37YZ": {
"title": "TypeScript Modules",

View File

@@ -24,6 +24,11 @@
"url": "https://www.businessballs.com/improving-workplace-performance/nudge-theory/",
"type": "article"
},
{
"title": "Nudge Theory Explained with Examples (on YouTube)",
"url": "https://www.youtube.com/watch?v=u3yxxteiyya&ab_channel=epm",
"type": "video"
},
{
"title": "Nudge Theory Explained in less than 10 minutes",
"url": "https://youtu.be/fA5eGIMZTRQ",
@@ -53,7 +58,7 @@
},
"2NlgbLeLBYwZX2u2rKkIO": {
"title": "BJ Fogg's Behavior Model",
"description": "B.J. Fogg, a renowned psychologist, and researcher at Stanford University, proposed the [Fogg Behavior Model (FBM)](https://www.behaviormodel.org/). This insightful model helps UX designers understand and influence user behavior by focusing on three core elements. These key factors are motivation, ability, and prompts.\n\n* **Motivation**: This element emphasizes the user's desire to perform a certain action or attain specific outcomes. Motivation can be linked to three core elements specified as sensation (pleasure/pain), anticipation (hope/fear), and social cohesion (belonging/rejection).\n \n* **Ability**: Ability refers to the user's capacity, both physical and mental, to perform desired actions. To enhance the ability of users, UX designers should follow the principle of simplicity. The easier it is to perform an action, the more likely users will engage with the product. Some factors to consider are time, financial resources, physical efforts, and cognitive load.\n \n* **Prompts**: Prompts are the cues, notifications, or triggers that signal users to take an action. For an action to occur, prompts should be presented at the right time when the user has adequate motivation and ability.\n \n\nUX designers should strive to find the balance between these three factors to facilitate the desired user behavior. By understanding your audience and their needs, implementing clear and concise prompts, and minimizing the effort required for action, the FBM can be an effective tool for designing user-centered products.",
"description": "B.J. Fogg, a renowned psychologist, and researcher at Stanford University, proposed the [Fogg Behavior Model (FBM)](https://www.behaviormodel.org/). This insightful model helps UX designers understand and influence user behavior by focusing on three core elements. These key factors are motivation, ability, and triggers.\n\n* **Motivation**: This element emphasizes the user's desire to perform a certain action or attain specific outcomes. Motivation can be linked to three core elements specified as sensation (pleasure/pain), anticipation (hope/fear), and social cohesion (belonging/rejection).\n \n* **Ability**: Ability refers to the user's capacity, both physical and mental, to perform desired actions. To enhance the ability of users, UX designers should follow the principle of simplicity. The easier it is to perform an action, the more likely users will engage with the product. Some factors to consider are time, financial resources, physical efforts, and cognitive load.\n \n* **Triggers**: Triggers are the cues, notifications, or prompts that signal users to take an action. For an action to occur, triggers should be presented at the right time when the user has adequate motivation and ability.\n \n\nUX designers should strive to find the balance between these three factors to facilitate the desired user behavior. By understanding your audience and their needs, implementing clear and concise triggers, and minimizing the effort required for action, the FBM can be an effective tool for designing user-centered products.",
"links": [
{
"title": "meaning of BJ fogg's behavior model",

View File

@@ -36,7 +36,7 @@ Here is the list of available roadmaps with more being actively worked upon.
- [Backend Roadmap](https://roadmap.sh/backend) / [Backend Beginner Roadmap](https://roadmap.sh/backend?r=backend-beginner)
- [DevOps Roadmap](https://roadmap.sh/devops) / [DevOps Beginner Roadmap](https://roadmap.sh/devops?r=devops-beginner)
- [Full Stack Roadmap](https://roadmap.sh/full-stack)
- [Git and GitHub](https://roadmap.sh/git-github)
- [Git and GitHub Roadmap](https://roadmap.sh/git-github)
- [API Design Roadmap](https://roadmap.sh/api-design)
- [Computer Science Roadmap](https://roadmap.sh/computer-science)
- [Data Structures and Algorithms Roadmap](https://roadmap.sh/datastructures-and-algorithms)

View File

@@ -1,31 +0,0 @@
import { type APIContext } from 'astro';
import { api } from './api.ts';
export type LeadeboardUserDetails = {
id: string;
name: string;
avatar?: string;
count: number;
};
export type ListLeaderboardStatsResponse = {
streaks: {
active: LeadeboardUserDetails[];
lifetime: LeadeboardUserDetails[];
};
projectSubmissions: {
currentMonth: LeadeboardUserDetails[];
lifetime: LeadeboardUserDetails[];
};
};
export function leaderboardApi(context: APIContext) {
return {
listLeaderboardStats: async function () {
return api(context).get<ListLeaderboardStatsResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-list-leaderboard-stats`,
{},
);
},
};
}

View File

@@ -1,15 +0,0 @@
import { type APIContext } from 'astro';
import { api } from './api.ts';
export function projectApi(context: APIContext) {
return {
listProjectsUserCount: async function (projectIds: string[]) {
return api(context).post<Record<string, number>>(
`${import.meta.env.PUBLIC_API_URL}/v1-list-projects-user-count`,
{
projectIds,
},
);
},
};
}

View File

@@ -1,7 +1,6 @@
import { type APIContext } from 'astro';
import { api } from './api.ts';
import type { RoadmapDocument } from '../components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
import type { PageType } from '../components/CommandMenu/CommandMenu.tsx';
export type ListShowcaseRoadmapResponse = {
data: Pick<
@@ -38,30 +37,3 @@ export function roadmapApi(context: APIContext) {
},
};
}
export type ProjectPageType = {
id: string;
title: string;
url: string;
};
export async function getProjectList() {
const baseUrl = import.meta.env.DEV
? 'http://localhost:3000'
: 'https://roadmap.sh';
const pages = await fetch(`${baseUrl}/pages.json`).catch((err) => {
console.error(err);
return [];
});
const pagesJson = await (pages as any).json();
const projects: ProjectPageType[] = pagesJson
.filter((page: any) => page?.group?.toLowerCase() === 'projects')
.map((page: any) => ({
id: page.id,
title: page.title,
url: page.url,
}));
return projects;
}

View File

@@ -1,7 +1,6 @@
import { type APIContext } from 'astro';
import { api } from './api.ts';
import type { ResourceType } from '../lib/resource-progress.ts';
import type { ProjectStatusDocument } from '../components/Projects/ListProjectSolutions.tsx';
export const allowedRoadmapVisibility = ['all', 'none', 'selected'] as const;
export type AllowedRoadmapVisibility =
@@ -100,7 +99,6 @@ export type GetPublicProfileResponse = Omit<
> & {
activity: UserActivityCount;
roadmaps: ProgressResponse[];
projects: ProjectStatusDocument[];
isOwnProfile: boolean;
};

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import { isLoggedIn } from '../../lib/jwt';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { Flame, X, Zap, ZapOff } from 'lucide-react';
import {Flame, X, Zap, ZapOff} from 'lucide-react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { StreakDay } from './StreakDay';
import {
@@ -11,7 +11,6 @@ import {
} from '../../stores/page.ts';
import { useStore } from '@nanostores/react';
import { cn } from '../../lib/classname.ts';
import { $accountStreak } from '../../stores/streak.ts';
type StreakResponse = {
count: number;
@@ -28,7 +27,12 @@ export function AccountStreak(props: AccountStreakProps) {
const dropdownRef = useRef(null);
const [isLoading, setIsLoading] = useState(true);
const accountStreak = useStore($accountStreak);
const [accountStreak, setAccountStreak] = useState<StreakResponse>({
count: 0,
longestCount: 0,
firstVisitAt: new Date(),
lastVisitAt: new Date(),
});
const [showDropdown, setShowDropdown] = useState(false);
const $roadmapsDropdownOpen = useStore(roadmapsDropdownOpen);
@@ -45,11 +49,6 @@ export function AccountStreak(props: AccountStreakProps) {
return;
}
if (accountStreak) {
setIsLoading(false);
return;
}
setIsLoading(true);
const { response, error } = await httpGet<StreakResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-streak`,
@@ -61,7 +60,7 @@ export function AccountStreak(props: AccountStreakProps) {
return;
}
$accountStreak.set(response);
setAccountStreak(response);
setIsLoading(false);
};
@@ -77,7 +76,7 @@ export function AccountStreak(props: AccountStreakProps) {
return null;
}
let { count: currentCount = 0 } = accountStreak || {};
let { count: currentCount } = accountStreak;
const previousCount =
accountStreak?.previousCount || accountStreak?.count || 0;
@@ -111,7 +110,7 @@ export function AccountStreak(props: AccountStreakProps) {
ref={dropdownRef}
className="absolute right-0 top-full z-50 w-[335px] translate-y-1 rounded-lg bg-slate-800 shadow-xl"
>
<div className="py-3 pl-4 pr-5">
<div className="pl-4 pr-5 py-3">
<div className="flex items-center justify-between gap-2 text-sm text-slate-500">
<p>
Current Streak
@@ -181,13 +180,8 @@ export function AccountStreak(props: AccountStreakProps) {
</div>
</div>
<p className="-mt-[0px] mb-[1.75px] text-center text-xs tracking-wide text-slate-600">
Visit every day to keep your streak going!
</p>
<p className='text-xs mt-1.5 text-center'>
<a href="/leaderboard" className="text-purple-400 hover:underline underline-offset-2">
See how you compare to others
</a>
<p className="text-center text-xs text-slate-600 tracking-wide mb-[1.75px] -mt-[0px]">
Visit every day to keep your streak alive!
</p>
</div>
</div>

View File

@@ -88,6 +88,7 @@ export function ActivityPage() {
}
const allProjects = response.filter((page) => page.group === 'Projects');
console.log(allProjects);
setProjectDetails(allProjects);
}

View File

@@ -10,21 +10,14 @@ type ProjectProgressType = {
projectStatus: ProjectStatusDocument & {
title: string;
};
showActions?: boolean;
userId?: string;
};
export function ProjectProgress(props: ProjectProgressType) {
const {
projectStatus,
showActions = true,
userId: defaultUserId = getUser()?.id,
} = props;
const { projectStatus } = props;
const userId = getUser()?.id;
const shouldShowActions =
projectStatus.submittedAt &&
projectStatus.submittedAt !== null &&
showActions;
projectStatus.submittedAt && projectStatus.submittedAt !== null;
return (
<div className="relative">
@@ -47,7 +40,7 @@ export function ProjectProgress(props: ProjectProgressType) {
{shouldShowActions && (
<div className="absolute right-2 top-0 flex h-full items-center">
<ProjectProgressActions
userId={defaultUserId!}
userId={userId!}
projectId={projectStatus.projectId}
/>
</div>

View File

@@ -1,227 +0,0 @@
import React, { useState } from 'react';
import { CheckIcon } from './ReactIcons/CheckIcon.tsx';
import { pageProgressMessage } from '../stores/page.ts';
import { httpPost } from '../lib/http.ts';
type InputProps = {
label: string;
name: string;
type: string;
value: string;
onChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => void;
required?: boolean;
rows?: number;
};
function Input(props: InputProps) {
const { label, name, type, value, onChange, required, rows } = props;
return (
<div className="mb-4">
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
{label} {required && <span className="text-red-500">*</span>}
</label>
{type === 'textarea' ? (
<textarea
placeholder={label}
id={name}
name={name}
value={value}
onChange={onChange}
rows={rows}
className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
autoComplete="off"
data-1p-ignore=""
data-form-type="other"
data-lpignore="true"
></textarea>
) : (
<input
type={type}
id={name}
placeholder={label}
name={name}
value={value}
onChange={onChange}
required={required}
className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
autoComplete="off"
data-1p-ignore=""
data-form-type="other"
data-lpignore="true"
/>
)}
</div>
);
}
export function AdvertiseForm() {
const [status, setStatus] = useState<'submitting' | 'submitted'>();
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
title: '',
company: '',
email: '',
phone: '',
message: '',
updates: false,
});
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value, type, checked } = e.target as any;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value,
});
};
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
pageProgressMessage.set('Please wait');
// Placeholder function to send data
console.log('Form data:', formData);
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-advertise`,
formData,
);
if (!response || error) {
pageProgressMessage.set('');
setError(error?.message || 'Something went wrong. Please try again.');
return;
}
setStatus('submitted');
pageProgressMessage.set('');
}
if (status === 'submitted') {
return (
<div className="flex flex-col items-center justify-center rounded-md border bg-gray-50 p-12 text-center">
<CheckIcon additionalClasses="h-12 w-12 text-green-500 mb-5" />
<h2 className="text-balance text-xl font-semibold text-gray-900">
Thank you for your interest in advertising with roadmap.sh
</h2>
<p className="mt-2 text-sm text-gray-500">
We will get back to you soon.
</p>
</div>
);
}
return (
<>
<h2 className="mb-5 text-balance text-2xl font-bold">
Ready to learn more? Fill out the form below to get started!
</h2>
{error && (
<div className="relative mb-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700">
{error}
</div>
)}
<form className="mb-5" onSubmit={handleSubmit}>
<div className="grid gap-0 sm:grid-cols-2 sm:gap-4">
<Input
label="First Name"
name="firstName"
type="text"
value={formData.firstName}
onChange={handleInputChange}
required
/>
<Input
label="Last Name"
name="lastName"
type="text"
value={formData.lastName}
onChange={handleInputChange}
required
/>
</div>
<div className="grid gap-0 sm:grid-cols-2 sm:gap-4">
<Input
label="Title"
name="title"
type="text"
value={formData.title}
onChange={handleInputChange}
required
/>
<Input
label="Company"
name="company"
type="text"
value={formData.company}
onChange={handleInputChange}
required
/>
</div>
<div className="grid gap-0 sm:grid-cols-2 sm:gap-4">
<Input
label="Email"
name="email"
type="email"
value={formData.email}
onChange={handleInputChange}
required
/>
<Input
label="Phone"
name="phone"
type="tel"
value={formData.phone}
onChange={handleInputChange}
/>
</div>
<Input
label="Message (Optional)"
name="message"
type="textarea"
value={formData.message}
onChange={handleInputChange}
rows={4}
/>
<div className="mb-4 flex items-start">
<div className="flex h-5 items-center">
<input
id="updates"
name="updates"
type="checkbox"
checked={formData.updates}
onChange={handleInputChange}
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
/>
</div>
<div className="ml-3 text-sm">
<label htmlFor="updates" className="font-medium text-gray-700">
I want to receive occasional updates about new products or
advertising opportunities with roadmap.sh
</label>
</div>
</div>
<div>
<button
type="submit"
className="flex justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Send
</button>
</div>
</form>
</>
);
}

View File

@@ -1,6 +1,6 @@
import Cookies from 'js-cookie';
import type { FormEvent } from 'react';
import { useId, useState } from 'react';
import { useState } from 'react';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
@@ -53,16 +53,12 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
setError(error?.message || 'Something went wrong. Please try again later.');
};
const emailFieldId = `form:${useId()}`;
const passwordFieldId = `form:${useId()}`;
return (
<form className="w-full" onSubmit={handleFormSubmit}>
<label htmlFor={emailFieldId} className="sr-only">
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
id={emailFieldId}
name="email"
type="email"
autoComplete="email"
@@ -72,11 +68,10 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
value={email}
onInput={(e) => setEmail(String((e.target as any).value))}
/>
<label htmlFor={passwordFieldId} className="sr-only">
<label htmlFor="password" className="sr-only">
Password
</label>
<input
id={passwordFieldId}
name="password"
type="password"
autoComplete="current-password"

View File

@@ -48,7 +48,6 @@ function handleGuest() {
'/team/members',
'/team/member',
'/team/settings',
'/dashboard',
];
showHideAuthElements('hide');

View File

@@ -122,7 +122,6 @@ export function CustomRoadmap(props: CustomRoadmapProps) {
{!isEmbed && <RoadmapHeader />}
<FlowRoadmapRenderer isEmbed={isEmbed} roadmap={roadmap!} />
<TopicDetail
resourceId={roadmap!._id}
resourceTitle={roadmap!.title}
resourceType="roadmap"
isEmbed={isEmbed}

View File

@@ -1,78 +0,0 @@
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import { DashboardCustomProgressCard } from './DashboardCustomProgressCard';
import { DashboardCardLink } from './DashboardCardLink';
import { useState } from 'react';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
import { Simulate } from 'react-dom/test-utils';
import { Bot, BrainCircuit, Map, PencilRuler } from 'lucide-react';
type DashboardAiRoadmapsProps = {
roadmaps: {
id: string;
title: string;
slug: string;
}[];
isLoading?: boolean;
};
export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
const { roadmaps, isLoading = false } = props;
return (
<>
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
AI Generated Roadmaps
</h2>
{!isLoading && roadmaps.length === 0 && (
<DashboardCardLink
className="mt-0"
icon={BrainCircuit}
href="/ai"
title="Generate Roadmaps with AI"
description="You can generate your own roadmap with AI"
/>
)}
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
{isLoading && (
<>
{Array.from({ length: 9 }).map((_, index) => (
<RoadmapCardSkeleton key={index} />
))}
</>
)}
{!isLoading && roadmaps.length > 0 && (
<>
{roadmaps.map((roadmap) => (
<a
href={`/ai/${roadmap.slug}`}
className="relative rounded-md border bg-white p-2.5 text-left text-sm shadow-sm truncate hover:border-gray-400 hover:bg-gray-50"
>
{roadmap.title}
</a>
))}
<a
className="flex items-center justify-center rounded-lg border border-dashed border-gray-300 bg-white p-2.5 text-sm font-medium text-gray-500 hover:bg-gray-50 hover:text-gray-600"
href={'/ai'}
>
+ Generate New
</a>
</>
)}
</div>
</>
);
}
type CustomProgressCardSkeletonProps = {};
function RoadmapCardSkeleton(
props: CustomProgressCardSkeletonProps,
) {
return (
<div className="h-[42px] w-full animate-pulse rounded-md bg-gray-200" />
);
}

View File

@@ -1,36 +0,0 @@
import { Bookmark } from 'lucide-react';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
type DashboardBookmarkCardProps = {
bookmark: UserProgress;
};
export function DashboardBookmarkCard(props: DashboardBookmarkCardProps) {
const {
resourceType,
resourceId,
resourceTitle,
roadmapSlug,
isCustomResource,
} = props.bookmark;
let url =
resourceType === 'roadmap'
? `/${resourceId}`
: `/best-practices/${resourceId}`;
if (isCustomResource) {
url = `/r/${roadmapSlug}`;
}
return (
<a
href={url}
key={resourceId}
className="group relative flex flex-row items-center gap-2 rounded-md border border-gray-300 bg-white px-1.5 py-2 text-left text-sm transition-all hover:border-gray-400"
>
<Bookmark className="size-4 fill-current text-gray-300" />
<h4 className="truncate text-gray-900">{resourceTitle}</h4>
</a>
);
}

View File

@@ -1,30 +0,0 @@
import { ArrowUpRight, type LucideIcon } from 'lucide-react';
import { cn } from '../../lib/classname';
type DashboardCardLinkProps = {
href: string;
title: string;
icon: LucideIcon;
description: string;
className?: string;
};
export function DashboardCardLink(props: DashboardCardLinkProps) {
const { href, title, description, icon: Icon, className } = props;
return (
<a
className={cn(
'relative mt-4 flex min-h-[220px] flex-col justify-end rounded-lg border border-gray-300 bg-gradient-to-br from-white to-gray-50 py-5 px-6 hover:border-gray-400 hover:from-white hover:to-gray-100',
className,
)}
href={href}
target="_blank"
>
<Icon className="mb-4 size-10 text-gray-300" strokeWidth={1.25} />
<h4 className="text-xl font-semibold tracking-wide">{title}</h4>
<p className="mt-1 text-gray-500">{description}</p>
<ArrowUpRight className="absolute right-3 top-3 size-4" />
</a>
);
}

View File

@@ -1,64 +0,0 @@
import { getPercentage } from '../../helper/number';
import { getRelativeTimeString } from '../../lib/date';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
type DashboardCustomProgressCardProps = {
progress: UserProgress;
};
export function DashboardCustomProgressCard(props: DashboardCustomProgressCardProps) {
const { progress } = props;
const {
resourceType,
resourceId,
resourceTitle,
total: totalCount,
done: doneCount,
skipped: skippedCount,
roadmapSlug,
isCustomResource,
updatedAt,
} = progress;
let url =
resourceType === 'roadmap'
? `/${resourceId}`
: `/best-practices/${resourceId}`;
if (isCustomResource) {
url = `/r/${roadmapSlug}`;
}
const totalMarked = doneCount + skippedCount;
const progressPercentage = getPercentage(totalMarked, totalCount);
return (
<a
href={url}
className="group relative flex min-h-[80px] w-full flex-col justify-between overflow-hidden rounded-md border bg-white p-3 text-left text-sm shadow-sm transition-all hover:border-gray-400 hover:bg-gray-50"
>
<h4 className="truncate font-medium text-gray-900">{resourceTitle}</h4>
<div className="mt-6 flex items-center gap-2">
<div className="h-2 w-full overflow-hidden rounded-md bg-black/10">
<div
className="h-full bg-black/20"
style={{ width: `${progressPercentage}%` }}
></div>
</div>
<span className="text-xs text-gray-500">
{Math.floor(+progressPercentage)}%
</span>
</div>
<p className="mt-1 text-xs text-gray-400">
{isCustomResource ? (
<>Last updated {getRelativeTimeString(updatedAt)}</>
) : (
<>Last practiced {getRelativeTimeString(updatedAt)}</>
)}
</p>
</a>
);
}

View File

@@ -1,124 +0,0 @@
import { useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { useStore } from '@nanostores/react';
import { $teamList } from '../../stores/team';
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown';
import { DashboardTab } from './DashboardTab';
import { PersonalDashboard, type BuiltInRoadmap } from './PersonalDashboard';
import { TeamDashboard } from './TeamDashboard';
import { getUser } from '../../lib/jwt';
type DashboardPageProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];
builtInSkillRoadmaps?: BuiltInRoadmap[];
builtInBestPractices?: BuiltInRoadmap[];
};
export function DashboardPage(props: DashboardPageProps) {
const { builtInRoleRoadmaps, builtInBestPractices, builtInSkillRoadmaps } =
props;
const currentUser = getUser();
const toast = useToast();
const teamList = useStore($teamList);
const [isLoading, setIsLoading] = useState(true);
const [selectedTeamId, setSelectedTeamId] = useState<string>();
async function getAllTeams() {
if (teamList.length > 0) {
return;
}
const { response, error } = await httpGet<TeamListResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-user-teams`,
);
if (error || !response) {
toast.error(error?.message || 'Something went wrong');
return;
}
$teamList.set(response);
}
useEffect(() => {
getAllTeams().finally(() => setIsLoading(false));
}, []);
const userAvatar =
currentUser?.avatar && !isLoading
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${currentUser.avatar}`
: '/images/default-avatar.png';
return (
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
<div className="container">
<div className="mb-6 sm:mb-8 flex flex-wrap items-center gap-1.5">
<DashboardTab
label="Personal"
isActive={!selectedTeamId}
onClick={() => setSelectedTeamId(undefined)}
avatar={userAvatar}
/>
{isLoading && (
<>
<DashboardTabSkeleton />
<DashboardTabSkeleton />
</>
)}
{!isLoading && (
<>
{teamList.map((team) => {
const { avatar } = team;
const avatarUrl = avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
return (
<DashboardTab
key={team._id}
label={team.name}
isActive={team._id === selectedTeamId}
{...(team.status === 'invited'
? {
href: `/respond-invite?i=${team.memberId}`,
}
: {
href: `/team/activity?t=${team._id}`,
// onClick: () => {
// setSelectedTeamId(team._id);
// },
})}
avatar={avatarUrl}
/>
);
})}
<DashboardTab
label="+ Create Team"
isActive={false}
href="/team/new"
className="border border-dashed border-gray-300 bg-transparent px-3 text-[13px] text-sm text-gray-500 hover:border-gray-600 hover:text-black"
/>
</>
)}
</div>
{!selectedTeamId && (
<PersonalDashboard
builtInRoleRoadmaps={builtInRoleRoadmaps}
builtInSkillRoadmaps={builtInSkillRoadmaps}
builtInBestPractices={builtInBestPractices}
/>
)}
{selectedTeamId && <TeamDashboard teamId={selectedTeamId} />}
</div>
</div>
);
}
function DashboardTabSkeleton() {
return (
<div className="h-[30px] w-[114px] animate-pulse rounded-md border bg-white"></div>
);
}

View File

@@ -1,54 +0,0 @@
import { getPercentage } from '../../helper/number';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import { ArrowUpRight, ExternalLink } from 'lucide-react';
type DashboardProgressCardProps = {
progress: UserProgress;
};
export function DashboardProgressCard(props: DashboardProgressCardProps) {
const { progress } = props;
const {
resourceType,
resourceId,
resourceTitle,
total: totalCount,
done: doneCount,
skipped: skippedCount,
roadmapSlug,
isCustomResource,
updatedAt,
} = progress;
let url =
resourceType === 'roadmap'
? `/${resourceId}`
: `/best-practices/${resourceId}`;
if (isCustomResource) {
url = `/r/${roadmapSlug}`;
}
const totalMarked = doneCount + skippedCount;
const progressPercentage = getPercentage(totalMarked, totalCount);
return (
<a
href={url}
key={resourceId}
className="group relative flex w-full items-center justify-between overflow-hidden rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm transition-all hover:border-gray-400"
>
<span className="flex-grow truncate">{resourceTitle}</span>
<span className="text-xs text-gray-400">
{parseInt(progressPercentage, 10)}%
</span>
<span
className="absolute left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 transition-colors group-hover:bg-black/10"
style={{
width: `${progressPercentage}%`,
}}
/>
</a>
);
}

View File

@@ -1,55 +0,0 @@
import { Check, CircleCheck, CircleDashed } from 'lucide-react';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import { cn } from '../../lib/classname.ts';
import { getRelativeTimeString } from '../../lib/date.ts';
type DashboardProjectCardProps = {
project: ProjectStatusDocument & {
title: string;
};
};
export function DashboardProjectCard(props: DashboardProjectCardProps) {
const { project } = props;
const { title, projectId, submittedAt, startedAt, repositoryUrl } = project;
const url = `/projects/${projectId}`;
const status = submittedAt && repositoryUrl ? 'submitted' : 'started';
return (
<a
href={url}
key={projectId}
className="group relative flex w-full items-center gap-2 text-left text-sm underline-offset-2"
>
<span
className={cn(
'flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full',
{
'border border-green-500 bg-green-500 group-hover:border-green-600 group-hover:bg-green-600':
status === 'submitted',
'border border-dashed border-gray-400 bg-transparent group-hover:border-gray-500':
status === 'started',
},
)}
>
{status === 'submitted' && (
<Check
className="relative top-[0.5px] size-3 text-white"
strokeWidth={3}
/>
)}
</span>
<span className="flex-grow truncate group-hover:underline">{title.replace(/(System)|(Service)/, '')}</span>
<span className="flex-shrink-0 bg-transparent text-xs text-gray-400 no-underline">
{!!startedAt &&
status === 'started' &&
getRelativeTimeString(startedAt)}
{!!submittedAt &&
status === 'submitted' &&
getRelativeTimeString(submittedAt)}
</span>
</a>
);
}

View File

@@ -1,40 +0,0 @@
import type { ReactNode } from 'react';
import { cn } from '../../lib/classname';
type DashboardTabProps = {
label: string | ReactNode;
isActive: boolean;
onClick?: () => void;
className?: string;
href?: string;
avatar?: string;
icon?: ReactNode;
};
export function DashboardTab(props: DashboardTabProps) {
const { isActive, onClick, label, className, href, avatar, icon } = props;
const Slot = href ? 'a' : 'button';
return (
<Slot
onClick={onClick}
className={cn(
'flex h-[30px] shrink-0 items-center gap-1 rounded-md border bg-white p-1.5 px-2 text-sm leading-none text-gray-600',
isActive ? 'border-gray-500 bg-gray-200 text-gray-900' : '',
className,
)}
{...(href ? { href } : {})}
>
{avatar && (
<img
src={avatar}
alt="avatar"
className="h-4 w-4 mr-0.5 rounded-full object-cover"
/>
)}
{icon}
{label}
</Slot>
);
}

View File

@@ -1,32 +0,0 @@
type EmptyStackMessageProps = {
number: number;
title: string;
description: string;
buttonText: string;
buttonLink: string;
};
export function EmptyStackMessage(props: EmptyStackMessageProps) {
const { number, title, description, buttonText, buttonLink } = props;
return (
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-black/50">
<div className="flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm">
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300 text-white">
{number}
</span>
<div className="my-3 text-center">
<h3 className="text-sm font-medium text-black">{title}</h3>
<p className="text-center text-xs text-gray-500">{description}</p>
</div>
<a
href={buttonLink}
className="rounded-md bg-black px-3 py-1 text-xs text-white transition-transform hover:scale-105 hover:bg-gray-900"
>
{buttonText}
</a>
</div>
</div>
);
}

View File

@@ -1,112 +0,0 @@
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import { DashboardCustomProgressCard } from './DashboardCustomProgressCard';
import { DashboardCardLink } from './DashboardCardLink';
import { useState } from 'react';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
import { Simulate } from 'react-dom/test-utils';
import {Bot, BrainCircuit, Map, PencilRuler} from 'lucide-react';
type ListDashboardCustomProgressProps = {
progresses: UserProgress[];
isLoading?: boolean;
isCustomResources?: boolean;
isAIGeneratedRoadmaps?: boolean;
};
export function ListDashboardCustomProgress(
props: ListDashboardCustomProgressProps,
) {
const {
progresses,
isLoading = false,
isAIGeneratedRoadmaps = false,
} = props;
const [isCreateCustomRoadmapModalOpen, setIsCreateCustomRoadmapModalOpen] =
useState(false);
const customRoadmapModal = isCreateCustomRoadmapModalOpen ? (
<CreateRoadmapModal
onClose={() => setIsCreateCustomRoadmapModalOpen(false)}
onCreated={(roadmap) => {
window.location.href = `${
import.meta.env.PUBLIC_EDITOR_APP_URL
}/${roadmap?._id}`;
return;
}}
/>
) : null;
return (
<>
{customRoadmapModal}
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
{isAIGeneratedRoadmaps ? 'AI Generated Roadmaps' : 'Custom Roadmaps'}
</h2>
{!isLoading && progresses.length === 0 && isAIGeneratedRoadmaps && (
<DashboardCardLink
className="mt-0"
icon={BrainCircuit}
href="/ai"
title="Generate Roadmaps with AI"
description="You can generate your own roadmap with AI"
/>
)}
{!isLoading && progresses.length === 0 && !isAIGeneratedRoadmaps && (
<DashboardCardLink
className="mt-0"
icon={PencilRuler}
href="https://draw.roadmap.sh"
title="Draw your own Roadmap"
description="Use our editor to draw your own roadmap"
/>
)}
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-4">
{isLoading && (
<>
{Array.from({ length: 8 }).map((_, index) => (
<CustomProgressCardSkeleton key={index} />
))}
</>
)}
{!isLoading && progresses.length > 0 && (
<>
{progresses.map((progress) => (
<DashboardCustomProgressCard
key={progress.resourceId}
progress={progress}
/>
))}
<a
className="flex min-h-[80px] items-center justify-center rounded-lg border border-dashed border-gray-300 bg-white p-4 text-sm font-medium text-gray-500 hover:bg-gray-50 hover:text-gray-600"
href={'/ai'}
onClick={(e) => {
if (!isAIGeneratedRoadmaps) {
e.preventDefault();
setIsCreateCustomRoadmapModalOpen(true);
}
}}
>
{isAIGeneratedRoadmaps ? '+ Generate New' : '+ Create New'}
</a>
</>
)}
</div>
</>
);
}
type CustomProgressCardSkeletonProps = {};
export function CustomProgressCardSkeleton(
props: CustomProgressCardSkeletonProps,
) {
return (
<div className="h-[106px] w-full animate-pulse rounded-md bg-gray-200" />
);
}

View File

@@ -1,14 +0,0 @@
type LoadingProgressProps = {};
export function LoadingProgress(props: LoadingProgressProps) {
return (
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-3">
{Array.from({ length: 6 }).map((_, index) => (
<div
key={index}
className="h-[38px] w-full animate-pulse rounded-md border border-gray-300 bg-gray-100"
></div>
))}
</div>
);
}

View File

@@ -1,350 +0,0 @@
import { type JSXElementConstructor, useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import type { PageType } from '../CommandMenu/CommandMenu';
import { useToast } from '../../hooks/use-toast';
import { getCurrentPeriod } from '../../lib/date';
import { ListDashboardCustomProgress } from './ListDashboardCustomProgress';
import { RecommendedRoadmaps } from './RecommendedRoadmaps';
import { ProgressStack } from './ProgressStack';
import { useStore } from '@nanostores/react';
import { $accountStreak, type StreakResponse } from '../../stores/streak';
import { CheckEmoji } from '../ReactIcons/CheckEmoji.tsx';
import { ConstructionEmoji } from '../ReactIcons/ConstructionEmoji.tsx';
import { BookEmoji } from '../ReactIcons/BookEmoji.tsx';
import { DashboardAiRoadmaps } from './DashboardAiRoadmaps.tsx';
type UserDashboardResponse = {
name: string;
email: string;
avatar: string;
headline: string;
username: string;
progresses: UserProgress[];
projects: ProjectStatusDocument[];
aiRoadmaps: {
id: string;
title: string;
slug: string;
}[];
topicDoneToday: number;
};
export type BuiltInRoadmap = {
id: string;
url: string;
title: string;
description: string;
isFavorite?: boolean;
relatedRoadmapIds?: string[];
};
type PersonalDashboardProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];
builtInSkillRoadmaps?: BuiltInRoadmap[];
builtInBestPractices?: BuiltInRoadmap[];
};
export function PersonalDashboard(props: PersonalDashboardProps) {
const {
builtInRoleRoadmaps = [],
builtInBestPractices = [],
builtInSkillRoadmaps = [],
} = props;
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
const [personalDashboardDetails, setPersonalDashboardDetails] =
useState<UserDashboardResponse>();
const [projectDetails, setProjectDetails] = useState<PageType[]>([]);
const accountStreak = useStore($accountStreak);
const loadAccountStreak = async () => {
if (accountStreak) {
return;
}
setIsLoading(true);
const { response, error } = await httpGet<StreakResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-streak`,
);
if (error || !response) {
toast.error(error?.message || 'Failed to load account streak');
return;
}
$accountStreak.set(response);
};
async function loadProgress() {
const { response: progressList, error } =
await httpGet<UserDashboardResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-user-dashboard`,
);
if (error || !progressList) {
return;
}
progressList?.progresses?.forEach((progress) => {
window.dispatchEvent(
new CustomEvent('mark-favorite', {
detail: {
resourceId: progress.resourceId,
resourceType: progress.resourceType,
isFavorite: progress.isFavorite,
},
}),
);
});
setPersonalDashboardDetails(progressList);
}
async function loadAllProjectDetails() {
const { error, response } = await httpGet<PageType[]>(`/pages.json`);
if (error) {
toast.error(error.message || 'Something went wrong');
return;
}
if (!response) {
return [];
}
const allProjects = response.filter((page) => page.group === 'Projects');
setProjectDetails(allProjects);
}
useEffect(() => {
Promise.allSettled([
loadProgress(),
loadAllProjectDetails(),
loadAccountStreak(),
]).finally(() => setIsLoading(false));
}, []);
useEffect(() => {
window.addEventListener('refresh-favorites', loadProgress);
return () => window.removeEventListener('refresh-favorites', loadProgress);
}, []);
const learningRoadmapsToShow = (personalDashboardDetails?.progresses || [])
.filter((progress) => !progress.isCustomResource)
.sort((a, b) => {
const updatedAtA = new Date(a.updatedAt);
const updatedAtB = new Date(b.updatedAt);
if (a.isFavorite && !b.isFavorite) {
return -1;
}
if (!a.isFavorite && b.isFavorite) {
return 1;
}
return updatedAtB.getTime() - updatedAtA.getTime();
});
const aiGeneratedRoadmaps = personalDashboardDetails?.aiRoadmaps || [];
const customRoadmaps = (personalDashboardDetails?.progresses || [])
.filter((progress) => progress.isCustomResource)
.sort((a, b) => {
const updatedAtA = new Date(a.updatedAt);
const updatedAtB = new Date(b.updatedAt);
return updatedAtB.getTime() - updatedAtA.getTime();
});
const { avatar, name } = personalDashboardDetails || {};
const avatarLink = avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
const allRoadmapsAndBestPractices = [
...builtInRoleRoadmaps,
...builtInSkillRoadmaps,
...builtInBestPractices,
];
const relatedRoadmapIds = allRoadmapsAndBestPractices
// take the ones that user is learning
.filter((roadmap) =>
learningRoadmapsToShow?.some(
(learningRoadmap) => learningRoadmap.resourceId === roadmap.id,
),
)
.flatMap((roadmap) => roadmap.relatedRoadmapIds)
// remove the ones that user is already learning or has bookmarked
.filter(
(roadmapId) =>
!learningRoadmapsToShow.some((lr) => lr.resourceId === roadmapId),
);
const recommendedRoadmapIds = new Set(
relatedRoadmapIds.length === 0
? [
'frontend',
'backend',
'devops',
'ai-data-scientist',
'full-stack',
'api-design',
]
: relatedRoadmapIds,
);
const recommendedRoadmaps = allRoadmapsAndBestPractices.filter((roadmap) =>
recommendedRoadmapIds.has(roadmap.id),
);
const enrichedProjects = personalDashboardDetails?.projects
.map((project) => {
const projectDetail = projectDetails.find(
(page) => page.id === project.projectId,
);
return {
...project,
title: projectDetail?.title || 'N/A',
};
})
.sort((a, b) => {
if (a.repositoryUrl && !b.repositoryUrl) {
return 1;
}
if (!a.repositoryUrl && b.repositoryUrl) {
return -1;
}
return 0;
});
return (
<section>
{isLoading ? (
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
) : (
<div className="flex items-start sm:items-center justify-between flex-col sm:flex-row gap-1">
<h2 className="text-lg font-medium">
Hi {name}, good {getCurrentPeriod()}!
</h2>
<a
href="/home"
className="text-xs text-purple-600 underline underline-offset-2 hover:text-purple-700"
>
Looking for old homepage? Click here
</a>
</div>
)}
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-4">
{isLoading ? (
<>
<DashboardCardSkeleton />
<DashboardCardSkeleton />
<DashboardCardSkeleton />
<DashboardCardSkeleton />
</>
) : (
<>
<DashboardCard
imgUrl={avatarLink}
title={name!}
description="Setup your profile"
href="/account/update-profile"
/>
<DashboardCard
icon={BookEmoji}
title="Visit Roadmaps"
description="Learn new skills"
href="/roadmaps"
/>
<DashboardCard
icon={ConstructionEmoji}
title="Build Projects"
description="Practice what you learn"
href="/projects"
/>
<DashboardCard
icon={CheckEmoji}
title="Best Practices"
description="Do things right way"
href="/best-practices"
/>
</>
)}
</div>
<ProgressStack
progresses={learningRoadmapsToShow}
projects={enrichedProjects || []}
isLoading={isLoading}
accountStreak={accountStreak}
topicDoneToday={personalDashboardDetails?.topicDoneToday || 0}
/>
<ListDashboardCustomProgress
progresses={customRoadmaps}
isLoading={isLoading}
/>
<DashboardAiRoadmaps
roadmaps={aiGeneratedRoadmaps}
isLoading={isLoading}
/>
<RecommendedRoadmaps
roadmaps={recommendedRoadmaps}
isLoading={isLoading}
/>
</section>
);
}
type DashboardCardProps = {
icon?: JSXElementConstructor<any>;
imgUrl?: string;
title: string;
description: string;
href: string;
};
function DashboardCard(props: DashboardCardProps) {
const { icon: Icon, imgUrl, title, description, href } = props;
return (
<a
href={href}
target="_blank"
className="flex flex-col overflow-hidden rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50"
>
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
</div>
)}
{imgUrl && (
<div className="px-4 pb-1.5 pt-3.5">
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
</div>
)}
<div className="flex grow flex-col justify-center gap-0.5 p-4">
<h3 className="truncate font-medium text-black">{title}</h3>
<p className="text-xs text-black">{description}</p>
</div>
</a>
);
}
function DashboardCardSkeleton() {
return (
<div className="h-[128px] animate-pulse rounded-lg border border-gray-300 bg-white"></div>
);
}

View File

@@ -1,370 +0,0 @@
import {
ArrowUpRight,
Bookmark,
FolderKanban,
type LucideIcon,
Map,
} from 'lucide-react';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import { DashboardBookmarkCard } from './DashboardBookmarkCard';
import { DashboardProjectCard } from './DashboardProjectCard';
import { useState } from 'react';
import { cn } from '../../lib/classname';
import { DashboardProgressCard } from './DashboardProgressCard';
import { useStore } from '@nanostores/react';
import { $accountStreak, type StreakResponse } from '../../stores/streak';
import { EmptyStackMessage } from './EmptyStackMessage.tsx';
type ProgressStackProps = {
progresses: UserProgress[];
projects: (ProjectStatusDocument & {
title: string;
})[];
accountStreak?: StreakResponse;
isLoading: boolean;
topicDoneToday: number;
};
const MAX_PROGRESS_TO_SHOW = 5;
const MAX_BOOKMARKS_TO_SHOW = 5;
const MAX_PROJECTS_TO_SHOW = 8;
type ProgressLaneProps = {
title: string;
linkText?: string;
linkHref?: string;
isLoading?: boolean;
isEmpty?: boolean;
loadingSkeletonCount?: number;
loadingSkeletonClassName?: string;
children: React.ReactNode;
emptyMessage?: string;
emptyIcon?: LucideIcon;
emptyLinkText?: string;
emptyLinkHref?: string;
};
function ProgressLane(props: ProgressLaneProps) {
const {
title,
linkText,
linkHref,
isLoading = false,
loadingSkeletonCount = 4,
loadingSkeletonClassName = '',
children,
isEmpty = false,
emptyIcon: EmptyIcon = Map,
emptyMessage = `No ${title.toLowerCase()} to show`,
emptyLinkHref = '/roadmaps',
emptyLinkText = 'Explore',
} = props;
return (
<div className="flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm">
{isLoading && (
<div className={'flex flex-row justify-between'}>
<div className="h-[16px] w-[75px] animate-pulse rounded-md bg-gray-100"></div>
</div>
)}
{!isLoading && !isEmpty && (
<div className="flex items-center justify-between gap-2">
<h3 className="text-xs uppercase text-gray-500">{title}</h3>
{linkText && linkHref && (
<a
href={linkHref}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-black"
>
<ArrowUpRight size={12} />
{linkText}
</a>
)}
</div>
)}
<div className="mt-4 flex flex-grow flex-col gap-1.5">
{isLoading && (
<>
{Array.from({ length: loadingSkeletonCount }).map((_, index) => (
<CardSkeleton key={index} className={loadingSkeletonClassName} />
))}
</>
)}
{!isLoading && children}
{!isLoading && isEmpty && (
<div className="flex flex-grow flex-col items-center justify-center text-gray-500">
<EmptyIcon
size={37}
strokeWidth={1.5}
className={'mb-3 text-gray-200'}
/>
<span className="mb-0.5 text-sm">{emptyMessage}</span>
<a
href={emptyLinkHref}
className="text-xs font-medium text-gray-600 underline-offset-2 hover:underline"
>
{emptyLinkText}
</a>
</div>
)}
</div>
</div>
);
}
export function ProgressStack(props: ProgressStackProps) {
const { progresses, projects, isLoading, accountStreak, topicDoneToday } =
props;
const bookmarkedProgresses = progresses.filter(
(progress) => progress?.isFavorite,
);
const userProgresses = progresses.filter(
(progress) => !progress?.isFavorite || progress?.done > 0,
);
const [showAllProgresses, setShowAllProgresses] = useState(false);
const userProgressesToShow = showAllProgresses
? userProgresses
: userProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
const [showAllProjects, setShowAllProjects] = useState(false);
const projectsToShow = showAllProjects
? projects
: projects.slice(0, MAX_PROJECTS_TO_SHOW);
const [showAllBookmarks, setShowAllBookmarks] = useState(false);
const bookmarksToShow = showAllBookmarks
? bookmarkedProgresses
: bookmarkedProgresses.slice(0, MAX_BOOKMARKS_TO_SHOW);
const totalProjectFinished = projects.filter(
(project) => project.repositoryUrl,
).length;
return (
<>
<div className="mt-2 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
<StatsCard
title="Current Streak"
value={accountStreak?.count || 0}
isLoading={isLoading}
/>
<StatsCard
title="Topics Done Today"
value={topicDoneToday}
isLoading={isLoading}
/>
<StatsCard
title="Projects Finished"
value={totalProjectFinished}
isLoading={isLoading}
/>
</div>
<div className="mt-2 grid min-h-[330px] grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
<div className="relative">
{!isLoading && bookmarksToShow.length === 0 && (
<EmptyStackMessage
number={1}
title={'Bookmark Roadmaps'}
description={'Bookmark some roadmaps to access them quickly'}
buttonText={'Explore Roadmaps'}
buttonLink={'/roadmaps'}
/>
)}
<ProgressLane
title={'Bookmarks'}
isLoading={isLoading}
loadingSkeletonCount={5}
linkHref={'/roadmaps'}
linkText={'Roadmaps'}
isEmpty={bookmarksToShow.length === 0}
emptyIcon={Bookmark}
emptyMessage={'No bookmarks to show'}
emptyLinkHref={'/roadmaps'}
emptyLinkText={'Explore Roadmaps'}
>
{bookmarksToShow.map((progress) => {
return (
<DashboardBookmarkCard
key={progress.resourceId}
bookmark={progress}
/>
);
})}
{bookmarkedProgresses.length > MAX_BOOKMARKS_TO_SHOW && (
<ShowAllButton
showAll={showAllBookmarks}
setShowAll={setShowAllBookmarks}
count={bookmarkedProgresses.length}
maxCount={MAX_BOOKMARKS_TO_SHOW}
className="mb-0.5 mt-3"
/>
)}
</ProgressLane>
</div>
<div className="relative">
{!isLoading && userProgressesToShow.length === 0 && (
<EmptyStackMessage
number={2}
title={'Track Progress'}
description={'Pick your first roadmap and start learning'}
buttonText={'Explore roadmaps'}
buttonLink={'/roadmaps'}
/>
)}
<ProgressLane
title={'Progress'}
linkHref={'/roadmaps'}
linkText={'Roadmaps'}
isLoading={isLoading}
loadingSkeletonCount={5}
isEmpty={userProgressesToShow.length === 0}
emptyMessage={'Update your Progress'}
emptyIcon={Map}
emptyLinkText={'Explore Roadmaps'}
>
{userProgressesToShow.length > 0 && (
<>
{userProgressesToShow.map((progress) => {
return (
<DashboardProgressCard
key={progress.resourceId}
progress={progress}
/>
);
})}
</>
)}
{userProgresses.length > MAX_PROGRESS_TO_SHOW && (
<ShowAllButton
showAll={showAllProgresses}
setShowAll={setShowAllProgresses}
count={userProgresses.length}
maxCount={MAX_PROGRESS_TO_SHOW}
className="mb-0.5 mt-3"
/>
)}
</ProgressLane>
</div>
<div className="relative">
<ProgressLane
title={'Projects'}
linkHref={'/projects'}
linkText={'Projects'}
isLoading={isLoading}
loadingSkeletonClassName={'h-5'}
loadingSkeletonCount={8}
isEmpty={projectsToShow.length === 0}
emptyMessage={'No projects started'}
emptyIcon={FolderKanban}
emptyLinkText={'Explore Projects'}
emptyLinkHref={'/projects'}
>
{!isLoading && projectsToShow.length === 0 && (
<EmptyStackMessage
number={3}
title={'Build your first project'}
description={'Pick a project to practice and start building'}
buttonText={'Explore Projects'}
buttonLink={'/projects'}
/>
)}
{projectsToShow.map((project) => {
return (
<DashboardProjectCard
key={project.projectId}
project={project}
/>
);
})}
{projects.length > MAX_PROJECTS_TO_SHOW && (
<ShowAllButton
showAll={showAllProjects}
setShowAll={setShowAllProjects}
count={projects.length}
maxCount={MAX_PROJECTS_TO_SHOW}
className="mb-0.5 mt-3"
/>
)}
</ProgressLane>
</div>
</div>
</>
);
}
type ShowAllButtonProps = {
showAll: boolean;
setShowAll: (showAll: boolean) => void;
count: number;
maxCount: number;
className?: string;
};
function ShowAllButton(props: ShowAllButtonProps) {
const { showAll, setShowAll, count, maxCount, className } = props;
return (
<span className="flex flex-grow items-end">
<button
className={cn(
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700',
className,
)}
onClick={() => setShowAll(!showAll)}
>
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>}
</button>
</span>
);
}
type CardSkeletonProps = {
className?: string;
};
function CardSkeleton(props: CardSkeletonProps) {
const { className } = props;
return (
<div
className={cn(
'h-10 w-full animate-pulse rounded-md bg-gray-100',
className,
)}
/>
);
}
type StatsCardProps = {
title: string;
value: number;
isLoading?: boolean;
};
function StatsCard(props: StatsCardProps) {
const { title, value, isLoading = false } = props;
return (
<div className="flex flex-col gap-1 rounded-md border bg-white p-4 shadow-sm">
<h3 className="mb-1 text-xs uppercase text-gray-500">{title}</h3>
{isLoading ? (
<CardSkeleton className="h-8" />
) : (
<span className="text-2xl font-medium text-black">{value}</span>
)}
</div>
);
}

View File

@@ -1,73 +0,0 @@
import type { BuiltInRoadmap } from './PersonalDashboard';
import { ArrowUpRight } from 'lucide-react';
import { MarkFavorite } from '../FeaturedItems/MarkFavorite.tsx';
type RecommendedRoadmapsProps = {
roadmaps: BuiltInRoadmap[];
isLoading: boolean;
};
export function RecommendedRoadmaps(props: RecommendedRoadmapsProps) {
const { roadmaps: roadmapsToShow, isLoading } = props;
return (
<>
<div className="mb-2 mt-8 flex items-center justify-between gap-2">
<h2 className="text-xs uppercase text-gray-400">
Recommended Roadmaps
</h2>
<a
href="/roadmaps"
className="flex items-center gap-1 rounded-full bg-gray-500 px-2 py-0.5 text-xs font-medium text-white transition-colors hover:bg-black"
>
<ArrowUpRight size={12} strokeWidth={2.5} />
All Roadmaps
</a>
</div>
{isLoading ? (
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
{Array.from({ length: 9 }).map((_, index) => (
<RecommendedCardSkeleton key={index} />
))}
</div>
) : (
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
{roadmapsToShow.map((roadmap) => (
<RecommendedRoadmapCard key={roadmap.id} roadmap={roadmap} />
))}
</div>
)}
<div className="mt-6 text-sm text-gray-500">
Need some help getting started? Check out our{' '}<a href="/get-started" className="text-blue-600 underline">Getting Started Guide</a>.
</div>
</>
);
}
type RecommendedRoadmapCardProps = {
roadmap: BuiltInRoadmap;
};
export function RecommendedRoadmapCard(props: RecommendedRoadmapCardProps) {
const { roadmap } = props;
const { title, url, description } = roadmap;
return (
<a
href={url}
className="font-regular text-sm sm:text-sm group relative block rounded-lg border border-gray-200 bg-white px-2.5 py-2 text-black no-underline hover:border-gray-400 hover:bg-gray-50"
>
<MarkFavorite className={'opacity-30'} resourceType={'roadmap'} resourceId={roadmap.id} />
{title}
</a>
);
}
function RecommendedCardSkeleton() {
return (
<div className="h-[38px] w-full animate-pulse rounded-md bg-gray-200" />
);
}

View File

@@ -1,165 +0,0 @@
import { useEffect, useState } from 'react';
import type { TeamMember } from '../TeamProgress/TeamProgressPage';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { getUser } from '../../lib/jwt';
import { LoadingProgress } from './LoadingProgress';
import { ResourceProgress } from '../Activity/ResourceProgress';
import { TeamActivityPage } from '../TeamActivity/TeamActivityPage';
import { cn } from '../../lib/classname';
import { Tooltip } from '../Tooltip';
type TeamDashboardProps = {
teamId: string;
};
export function TeamDashboard(props: TeamDashboardProps) {
const { teamId } = props;
const toast = useToast();
const currentUser = getUser();
const [isLoading, setIsLoading] = useState(true);
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
async function getTeamProgress() {
const { response, error } = await httpGet<TeamMember[]>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-progress/${teamId}`,
);
if (error || !response) {
toast.error(error?.message || 'Failed to get team progress');
return;
}
setTeamMembers(
response.sort((a, b) => {
if (a.email === currentUser?.email) {
return -1;
}
if (b.email === currentUser?.email) {
return 1;
}
return 0;
}),
);
}
useEffect(() => {
if (!teamId) {
return;
}
setIsLoading(true);
setTeamMembers([]);
getTeamProgress().finally(() => setIsLoading(false));
}, [teamId]);
if (!currentUser) {
return null;
}
const currentMember = teamMembers.find(
(member) => member.email === currentUser.email,
);
const learningRoadmapsToShow =
currentMember?.progress?.filter(
(progress) => progress.resourceType === 'roadmap',
) || [];
const allMembersWithoutCurrentUser = teamMembers.sort((a, b) => {
if (a.email === currentUser.email) {
return -1;
}
if (b.email === currentUser.email) {
return 1;
}
return 0;
});
return (
<section className="mt-8">
<h2 className="mb-3 text-xs uppercase text-gray-400">Roadmaps</h2>
{isLoading && <LoadingProgress />}
{!isLoading && learningRoadmapsToShow.length > 0 && (
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-3">
{learningRoadmapsToShow.map((roadmap) => {
const learningCount = roadmap.learning || 0;
const doneCount = roadmap.done || 0;
const totalCount = roadmap.total || 0;
const skippedCount = roadmap.skipped || 0;
return (
<ResourceProgress
key={roadmap.resourceId}
isCustomResource={roadmap?.isCustomResource || false}
doneCount={doneCount > totalCount ? totalCount : doneCount}
learningCount={
learningCount > totalCount ? totalCount : learningCount
}
totalCount={totalCount}
skippedCount={skippedCount}
resourceId={roadmap.resourceId}
resourceType="roadmap"
updatedAt={roadmap.updatedAt}
title={roadmap.resourceTitle}
showActions={false}
roadmapSlug={roadmap.roadmapSlug}
/>
);
})}
</div>
)}
<h2 className="mb-3 mt-6 text-xs uppercase text-gray-400">
Team Members
</h2>
{isLoading && <TeamMemberLoading className="mb-6" />}
{!isLoading && (
<div className="mb-6 flex flex-wrap gap-2">
{allMembersWithoutCurrentUser.map((member) => {
const avatar = member?.avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${member.avatar}`
: '/images/default-avatar.png';
return (
<span className="group relative" key={member.email}>
<figure className="relative aspect-square size-8 overflow-hidden rounded-md bg-gray-100">
<img
src={avatar}
alt={member.name || ''}
className="absolute inset-0 h-full w-full object-cover"
/>
</figure>
<Tooltip position="top-center" additionalClass="text-sm">
{member.name}
</Tooltip>
</span>
);
})}
</div>
)}
<TeamActivityPage teamId={teamId} />
</section>
);
}
type TeamMemberLoadingProps = {
className?: string;
};
function TeamMemberLoading(props: TeamMemberLoadingProps) {
const { className } = props;
return (
<div className={cn('flex flex-wrap gap-2', className)}>
{Array.from({ length: 15 }).map((_, index) => (
<div
key={index}
className="size-8 animate-pulse rounded-md bg-gray-200"
></div>
))}
</div>
);
}

View File

@@ -23,7 +23,7 @@ export function FeatureAnnouncement(props: FeatureAnnouncementProps) {
</span>
Projects are live on the{' '}
<a
href={'/projects'}
href={'/backend/projects'}
className="font-medium text-blue-500 underline underline-offset-2"
>
backend roadmap

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import type { MouseEvent } from 'react';
import type { MouseEvent } from "react";
import { httpPatch } from '../../lib/http';
import type { ResourceType } from '../../lib/resource-progress';
import { isLoggedIn } from '../../lib/jwt';
@@ -7,7 +7,6 @@ import { showLoginPopup } from '../../lib/popup';
import { FavoriteIcon } from './FavoriteIcon';
import { Spinner } from '../ReactIcons/Spinner';
import { useToast } from '../../hooks/use-toast';
import { cn } from '../../lib/classname';
type MarkFavoriteType = {
resourceType: ResourceType;
@@ -28,9 +27,7 @@ export function MarkFavorite({
const toast = useToast();
const [isLoading, setIsLoading] = useState(false);
const [isFavorite, setIsFavorite] = useState(
isAuthenticated
? (favorite ?? localStorage.getItem(localStorageKey) === '1')
: false,
isAuthenticated ? (favorite ?? localStorage.getItem(localStorageKey) === '1') : false
);
async function toggleFavoriteHandler(e: MouseEvent<HTMLButtonElement>) {
@@ -51,7 +48,7 @@ export function MarkFavorite({
{
resourceType,
resourceId,
},
}
);
if (error) {
@@ -71,7 +68,7 @@ export function MarkFavorite({
resourceType,
isFavorite: !isFavorite,
},
}),
})
);
window.dispatchEvent(new CustomEvent('refresh-favorites', {}));
@@ -102,18 +99,11 @@ export function MarkFavorite({
aria-label={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
onClick={toggleFavoriteHandler}
tabIndex={-1}
className={cn(
'absolute right-1.5 top-1.5 z-30 focus:outline-0',
isFavorite ? '' : 'opacity-30 hover:opacity-100',
className,
)}
data-is-favorite={isFavorite}
className={`${isFavorite ? '' : 'opacity-30 hover:opacity-100'} ${
className || 'absolute right-1.5 top-1.5 z-30 focus:outline-0'
}`}
>
{isLoading ? (
<Spinner isDualRing={false} />
) : (
<FavoriteIcon isFavorite={isFavorite} />
)}
{isLoading ? <Spinner isDualRing={false} /> : <FavoriteIcon isFavorite={isFavorite} />}
</button>
);
}

View File

@@ -35,8 +35,8 @@ import Icon from './AstroIcon.astro';
>
</p>
<div class='flex flex-col justify-between gap-8 lg:gap-2 lg:flex-row'>
<div class='max-w-[425px]'>
<div class='flex flex-col justify-between gap-12 sm:flex-row'>
<div class='max-w-[365px]'>
<p class='text-md flex items-center'>
<a
class='inline-flex items-center text-lg font-medium text-white transition-colors hover:text-gray-400'
@@ -56,7 +56,7 @@ import Icon from './AstroIcon.astro';
</a>
</p>
<p class='my-4 text-slate-300/60'>
Community created roadmaps, best practices, projects, articles, resources and journeys to help
Community created roadmaps, articles, resources and journeys to help
you choose your path and grow in your career.
</p>
<div class='text-sm text-gray-400'>
@@ -67,8 +67,6 @@ import Icon from './AstroIcon.astro';
<span class='mx-1.5'>&middot;</span>
<a href='/privacy' class='hover:text-white'>Privacy</a>
<span class='mx-1.5'>&middot;</span>
<a href='/advertise' class='hover:text-white'>Advertise</a>
<span class='mx-1.5'>&middot;</span>
<a
aria-label='Write us an email'
href='mailto:info@roadmap.sh'
@@ -99,19 +97,20 @@ import Icon from './AstroIcon.astro';
</div>
</div>
<div class='max-w-[340px] text-left lg:text-right'>
<div class='max-w-[365px] text-left sm:text-right'>
<a href='https://thenewstack.io' target='_blank'>
<img
src='/images/tns-sm.png'
alt='ThewNewStack'
class='my-1.5 mr-auto lg:ml-auto lg:mr-0'
class='my-1.5 mr-auto sm:ml-auto sm:mr-0'
width='200'
height='24.8'
loading="lazy"
/>
</a>
<p class='my-4 text-slate-300/60'>
The top DevOps resource for Kubernetes, cloud-native computing, and large-scale development and deployment.
The leading DevOps resource for Kubernetes, cloud-native computing,
and the latest in at-scale development, deployment, and management.
</p>
<div class='text-sm text-gray-400'>
<p>

View File

@@ -11,9 +11,7 @@ export function ProgressNudge(props: ProgressNudgeProps) {
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
const $roadmapProgress = useStore(roadmapProgress);
const done =
($roadmapProgress?.done?.length || 0) +
($roadmapProgress?.skipped?.length || 0);
const done = $roadmapProgress?.done?.length || 0;
const hasProgress = done > 0;
@@ -53,8 +51,7 @@ export function ProgressNudge(props: ProgressNudgeProps) {
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
Progress
</span>
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of{' '}
<span>{$totalRoadmapNodes}</span> Done
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of <span>{$totalRoadmapNodes}</span> Done
</span>
<span

View File

@@ -1,4 +1,5 @@
---
import { FavoriteRoadmaps } from './FavoriteRoadmaps';
import { FeatureAnnouncement } from "../FeatureAnnouncement";
---
@@ -30,4 +31,5 @@ import { FeatureAnnouncement } from "../FeatureAnnouncement";
their career.
</p>
</div>
<FavoriteRoadmaps client:only='react' />
</div>

View File

@@ -1,26 +0,0 @@
import type { AppError } from '../../api/api';
import { ErrorIcon } from '../ReactIcons/ErrorIcon';
type ErrorPageProps = {
error: AppError;
};
export function ErrorPage(props: ErrorPageProps) {
const { error } = props;
return (
<div className="min-h-screen bg-gray-50">
<div className="container py-10">
<div className="flex min-h-[250px] flex-col items-center justify-center px-5 py-3 sm:px-0 sm:py-20">
<ErrorIcon additionalClasses="mb-4 h-8 w-8 sm:h-14 sm:w-14" />
<h2 className="mb-1 text-lg font-semibold sm:text-xl">
Oops! Something went wrong
</h2>
<p className="mb-3 text-balance text-center text-xs text-gray-800 sm:text-sm">
{error?.message || 'An error occurred while fetching'}
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,178 +0,0 @@
import { useState, type ReactNode } from 'react';
import type {
LeadeboardUserDetails,
ListLeaderboardStatsResponse,
} from '../../api/leaderboard';
import { cn } from '../../lib/classname';
import { FolderKanban, Zap, Trophy } from 'lucide-react';
import { RankBadgeIcon } from '../ReactIcons/RankBadgeIcon';
import { TrophyEmoji } from '../ReactIcons/TrophyEmoji';
import { SecondPlaceMedalEmoji } from '../ReactIcons/SecondPlaceMedalEmoji';
import { ThirdPlaceMedalEmoji } from '../ReactIcons/ThirdPlaceMedalEmoji';
type LeaderboardPageProps = {
stats: ListLeaderboardStatsResponse;
};
export function LeaderboardPage(props: LeaderboardPageProps) {
const { stats } = props;
return (
<div className="min-h-screen bg-gray-50">
<div className="container py-10">
<div className="mb-8 text-center">
<div className="mb-2 flex items-center justify-center gap-3">
<Trophy className="size-8 text-yellow-500" />
<h2 className="text-2xl font-bold sm:text-3xl">Leaderboard</h2>
</div>
<p className="mx-auto max-w-2xl text-balance text-sm text-gray-500 sm:text-base">
Top users based on their activity on roadmap.sh
</p>
<div className="mt-8 grid gap-2 md:grid-cols-2">
<LeaderboardLane
title="Longest Visit Streak"
tabs={[
{
title: 'Active',
users: stats.streaks?.active || [],
emptyIcon: <Zap className="size-16 text-gray-300" />,
emptyText: 'No users with streaks yet',
},
{
title: 'Lifetime',
users: stats.streaks?.lifetime || [],
emptyIcon: <Zap className="size-16 text-gray-300" />,
emptyText: 'No users with streaks yet',
},
]}
/>
<LeaderboardLane
title="Projects Completed"
tabs={[
{
title: 'This Month',
users: stats.projectSubmissions.currentMonth,
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
emptyText: 'No projects submitted this month',
},
{
title: 'Lifetime',
users: stats.projectSubmissions.lifetime,
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
emptyText: 'No projects submitted yet',
},
]}
/>
</div>
</div>
</div>
</div>
);
}
type LeaderboardLaneProps = {
title: string;
tabs: {
title: string;
users: LeadeboardUserDetails[];
emptyIcon?: ReactNode;
emptyText?: string;
}[];
};
function LeaderboardLane(props: LeaderboardLaneProps) {
const { title, tabs } = props;
const [activeTab, setActiveTab] = useState(tabs[0]);
const { users: usersToShow, emptyIcon, emptyText } = activeTab;
return (
<div className="overflow-hidden rounded-md border bg-white shadow-sm">
<div className="flex items-center justify-between gap-2 bg-gray-100 px-3 py-3 mb-3">
<h3 className="text-base font-medium">{title}</h3>
{tabs.length > 1 && (
<div className="flex items-center gap-2">
{tabs.map((tab) => {
const isActive = tab === activeTab;
return (
<button
key={tab.title}
onClick={() => setActiveTab(tab)}
className={cn(
'text-sm font-medium underline-offset-2 transition-colors',
{
'text-black underline': isActive,
'text-gray-400 hover:text-gray-600': !isActive,
},
)}
>
{tab.title}
</button>
);
})}
</div>
)}
</div>
{usersToShow.length === 0 && emptyText && (
<div className="flex flex-col items-center justify-center p-8">
{emptyIcon}
<p className="mt-4 text-sm text-gray-500">{emptyText}</p>
</div>
)}
{usersToShow.length > 0 && (
<ul className="divide-y divide-gray-100 pb-4">
{usersToShow.map((user, counter) => {
const avatar = user?.avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
: '/images/default-avatar.png';
const rank = counter + 1;
return (
<li
key={user.id}
className="flex items-center justify-between gap-1 pl-2 pr-5 py-2.5 hover:bg-gray-50"
>
<div className="flex min-w-0 items-center gap-2">
<span
className={cn(
'relative text-xs mr-1 flex size-6 shrink-0 items-center justify-center rounded-full tabular-nums',
{
'text-black': rank <= 3,
'text-gray-400': rank > 3,
},
)}
>
{rank}
</span>
<img
src={avatar}
alt={user.name}
className="size-7 shrink-0 rounded-full"
/>
<span className="truncate">{user.name}</span>
{rank === 1 ? (
<TrophyEmoji className="size-5" />
) : rank === 2 ? (
<SecondPlaceMedalEmoji className="size-5" />
) : rank === 3 ? (
<ThirdPlaceMedalEmoji className="size-5" />
) : (
''
)}
</div>
<span className="text-sm text-gray-500">{user.count}</span>
</li>
);
})}
</ul>
)}
</div>
);
}

View File

@@ -25,7 +25,7 @@ const links = [
Icon: Waypoints,
},
{
link: '/projects',
link: '/backend/projects',
label: 'Projects',
description: 'Skill-up with real-world projects',
Icon: FolderKanban,
@@ -62,12 +62,6 @@ const links = [
Icon: Shirt,
isExternal: true,
},
{
link: '/advertise',
label: 'Advertise',
description: 'Promote your product or service',
Icon: Menu,
},
];
export function NavigationDropdown() {

View File

@@ -3,12 +3,9 @@ import type {
ProjectDifficultyType,
ProjectFileType,
} from '../../lib/project.ts';
import { Users } from 'lucide-react';
import { formatCommaNumber } from '../../lib/number.ts';
type ProjectCardProps = {
project: ProjectFileType;
userCount?: number;
};
const badgeVariants: Record<ProjectDifficultyType, string> = {
@@ -18,7 +15,7 @@ const badgeVariants: Record<ProjectDifficultyType, string> = {
};
export function ProjectCard(props: ProjectCardProps) {
const { project, userCount = 0 } = props;
const { project } = props;
const { frontmatter, id } = project;
@@ -34,18 +31,8 @@ export function ProjectCard(props: ProjectCardProps) {
/>
<Badge variant={'grey'} text={frontmatter.nature} />
</span>
<span className="my-3 flex flex-col">
<span className="mb-1 font-medium">{frontmatter.title}</span>
<span className="text-sm text-gray-500">{frontmatter.description}</span>
</span>
<span className="flex items-center gap-2 text-xs text-gray-400">
<Users className="inline-block size-3.5" />
{userCount > 0 ? (
<>{formatCommaNumber(userCount)} Started</>
) : (
<>Be the first to solve!</>
)}
</span>
<span className="mb-1 mt-2.5 font-medium">{frontmatter.title}</span>
<span className="text-sm text-gray-500">{frontmatter.description}</span>
</a>
);
}

View File

@@ -8,7 +8,7 @@ import {
type AllowedVoteType,
} from './ListProjectSolutions';
import { getRelativeTimeString } from '../../lib/date';
import { ArrowUpRight, ThumbsDown, ThumbsUp, Trophy } from 'lucide-react';
import { ArrowUpRight, ThumbsDown, ThumbsUp } from 'lucide-react';
import { VoteButton } from './VoteButton';
import { GitHubIcon } from '../ReactIcons/GitHubIcon';
import { isLoggedIn } from '../../lib/jwt';
@@ -131,58 +131,64 @@ export function ProjectSolutionModal(props: ProjectSolutionModalProps) {
deleteUrlParam('u');
window.location.reload();
}}
wrapperClassName={'max-w-lg'}
bodyClassName={'h-auto'}
>
<div className="relative p-6">
<h1 className="text-2xl text-balance mb-1 font-bold text-gray-900">{projectTitle}</h1>
<p className="text-sm text-balance text-gray-600">{projectDescription}</p>
<div className="relative p-4">
<h1 className="text-xl font-semibold">{projectTitle}</h1>
<p className="mt-1 max-w-xs text-sm text-gray-500">
{projectDescription}
</p>
<div className="my-5 rounded-lg bg-gray-100 p-4">
<div className="flex items-center gap-3">
<img
src={
avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png'
}
alt={solution?.user?.name}
className="h-12 w-12 rounded-full border-2 border-white shadow-md"
/>
<div>
<h2 className="text-lg font-semibold text-gray-900">{solution?.user.name}'s Solution</h2>
<p className="text-sm text-gray-600">
Submitted their solution{' '}
{getRelativeTimeString(solution?.submittedAt!)}
</p>
</div>
</div>
<hr className="-mx-4 my-4 border-gray-300" />
<div className="flex items-center gap-1.5">
<img
src={
avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png'
}
alt={solution?.user?.name}
className="mr-0.5 h-7 w-7 rounded-full"
/>
<span className="font-medium text-black">{solution?.user.name}</span>
<span className="hidden sm:inline">
{submittedAlternatives[
Math.floor(Math.random() * submittedAlternatives.length)
] || 'submitted their solution'}
</span>{' '}
<span className="flex-grow text-right text-gray-400 sm:flex-grow-0 sm:text-left sm:font-medium sm:text-black">
{getRelativeTimeString(solution?.submittedAt!)}
</span>
</div>
<div className="flex items-center justify-between">
<div className="mt-4 flex items-center justify-between gap-2">
<a
className="flex items-center gap-2 rounded-full bg-black px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-gray-800"
className="flex items-center gap-1 rounded-full border px-2 py-1 text-xs text-black transition-colors hover:border-black hover:bg-black hover:text-white"
href={solution?.repositoryUrl}
target="_blank"
>
<GitHubIcon className="h-5 w-5 text-current" />
View Solution on GitHub
<ArrowUpRight className="h-4 w-4" />
<GitHubIcon className="h-4 w-4 text-current" />
View Solution
</a>
<div className="flex overflow-hidden rounded-full border">
<div className="flex shrink-0 overflow-hidden rounded-full border">
<VoteButton
icon={ThumbsUp}
isActive={solution?.voteType === 'upvote'}
count={solution?.upvotes || 0}
onClick={() => handleSubmitVote(solution?.id!, 'upvote')}
onClick={() => {
handleSubmitVote(solution?.id!, 'upvote');
}}
/>
<VoteButton
icon={ThumbsDown}
isActive={solution?.voteType === 'downvote'}
count={solution?.downvotes || 0}
hideCount={true}
onClick={() => handleSubmitVote(solution?.id!, 'downvote')}
onClick={() => {
handleSubmitVote(solution?.id!, 'downvote');
}}
/>
</div>
</div>

View File

@@ -40,11 +40,10 @@ function DifficultyButton(props: DifficultyButtonProps) {
type ProjectsListProps = {
projects: ProjectFileType[];
userCounts: Record<string, number>;
};
export function ProjectsList(props: ProjectsListProps) {
const { projects, userCounts } = props;
const { projects } = props;
const { difficulty: urlDifficulty } = getUrlParams();
const [difficulty, setDifficulty] = useState<
@@ -128,10 +127,9 @@ export function ProjectsList(props: ProjectsListProps) {
.sort((a, b) => {
return a.frontmatter.sort - b.frontmatter.sort;
})
.map((matchingProject) => {
const count = userCounts[matchingProject?.id] || 0;
return <ProjectCard project={matchingProject} userCount={count} />;
})}
.map((matchingProject) => (
<ProjectCard project={matchingProject} />
))}
</div>
</div>
);

View File

@@ -1,200 +0,0 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/classname.ts';
import { Box, Filter, Group, X } from 'lucide-react';
import {
deleteUrlParam,
getUrlParams,
setUrlParams,
} from '../../lib/browser.ts';
import { CategoryFilterButton } from '../Roadmaps/CategoryFilterButton.tsx';
import {
projectDifficulties,
type ProjectFileType,
} from '../../lib/project.ts';
import { ProjectCard } from './ProjectCard.tsx';
type ProjectGroup = {
id: string;
title: string;
projects: ProjectFileType[];
};
type ProjectsPageProps = {
roadmapsProjects: ProjectGroup[];
userCounts: Record<string, number>;
};
export function ProjectsPage(props: ProjectsPageProps) {
const { roadmapsProjects, userCounts } = props;
const allUniqueProjectIds = new Set<string>(
roadmapsProjects.flatMap((group) =>
group.projects.map((project) => project.id),
),
);
const allUniqueProjects = useMemo(
() =>
Array.from(allUniqueProjectIds)
.map((id) =>
roadmapsProjects
.flatMap((group) => group.projects)
.find((project) => project.id === id),
)
.filter(Boolean) as ProjectFileType[],
[allUniqueProjectIds],
);
const [activeGroup, setActiveGroup] = useState<string>('');
const [visibleProjects, setVisibleProjects] =
useState<ProjectFileType[]>(allUniqueProjects);
const [isFilterOpen, setIsFilterOpen] = useState(false);
useEffect(() => {
const { g } = getUrlParams() as { g: string };
if (!g) {
return;
}
setActiveGroup(g);
const group = roadmapsProjects.find((group) => group.id === g);
if (!group) {
return;
}
setVisibleProjects(group.projects);
}, []);
const sortedVisibleProjects = useMemo(
() =>
visibleProjects.sort((a, b) => {
const projectADifficulty = a?.frontmatter.difficulty || 'beginner';
const projectBDifficulty = b?.frontmatter.difficulty || 'beginner';
return (
projectDifficulties.indexOf(projectADifficulty) -
projectDifficulties.indexOf(projectBDifficulty)
);
}),
[visibleProjects],
);
const activeGroupDetail = roadmapsProjects.find(
(group) => group.id === activeGroup,
);
const requiredSortOrder = [
'Frontend',
'Backend',
'DevOps',
'Full-stack',
'JavaScript',
'Go',
'Python',
'Node.js',
'Java',
];
return (
<div className="border-t bg-gray-100">
<button
onClick={() => {
setIsFilterOpen(!isFilterOpen);
}}
id="filter-button"
className={cn(
'-mt-1 flex w-full items-center justify-center bg-gray-300 py-2 text-sm text-black focus:shadow-none focus:outline-0 sm:hidden',
{
'mb-3': !isFilterOpen,
},
)}
>
{!isFilterOpen && <Filter size={13} className="mr-1" />}
{isFilterOpen && <X size={13} className="mr-1" />}
Categories
</button>
<div className="container relative flex flex-col gap-4 sm:flex-row">
<div
className={cn(
'hidden w-full flex-col from-gray-100 sm:w-[160px] sm:shrink-0 sm:border-r sm:bg-gradient-to-l sm:pt-6',
{
'hidden sm:flex': !isFilterOpen,
'z-50 flex': isFilterOpen,
},
)}
>
<div className="absolute top-0 -mx-4 w-full bg-white pb-0 shadow-xl sm:sticky sm:top-10 sm:mx-0 sm:bg-transparent sm:pb-20 sm:shadow-none">
<div className="grid grid-cols-1">
<CategoryFilterButton
onClick={() => {
setActiveGroup('');
setVisibleProjects(allUniqueProjects);
setIsFilterOpen(false);
deleteUrlParam('g');
}}
category={'All Projects'}
selected={activeGroup === ''}
/>
{roadmapsProjects
.sort((a, b) => {
const aIndex = requiredSortOrder.indexOf(a.title);
const bIndex = requiredSortOrder.indexOf(b.title);
if (aIndex === -1 && bIndex === -1) {
return a.title.localeCompare(b.title);
}
if (aIndex === -1) {
return 1;
}
if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
})
.map((group) => (
<CategoryFilterButton
key={group.id}
onClick={() => {
setActiveGroup(group.id);
setIsFilterOpen(false);
document
?.getElementById('filter-button')
?.scrollIntoView();
setVisibleProjects(group.projects);
setUrlParams({ g: group.id });
}}
category={group.title}
selected={activeGroup === group.id}
/>
))}
</div>
</div>
</div>
<div className="flex flex-grow flex-col pb-20 pt-2 sm:pt-6">
<div className="mb-4 flex items-center justify-between text-sm text-gray-500">
<h3 className={'flex items-center'}>
<Box size={15} className="mr-1" strokeWidth={2} />
{activeGroupDetail
? `Projects in ${activeGroupDetail?.title}`
: 'All Projects'}
</h3>
<p className="text-left">
Matches found ({sortedVisibleProjects.length})
</p>
</div>
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
{sortedVisibleProjects.map((project) => (
<ProjectCard
key={project.id}
project={project}
userCount={userCounts[project.id] || 0}
/>
))}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,17 +0,0 @@
import { isLoggedIn } from '../../lib/jwt.ts';
import { showLoginPopup } from '../../lib/popup.ts';
export function ProjectsPageHeader() {
return (
<div className="bg-white py-3 sm:py-12">
<div className="container">
<div className="flex flex-col items-start bg-white sm:items-center">
<h1 className="text-2xl font-bold sm:text-5xl">Project Ideas</h1>
<p className="mt-1 text-sm sm:mt-4 sm:text-lg">
Browse the ever-growing list of projects ideas and solutions.
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,11 +1,11 @@
import { Flag, Play, Send, Share, Square, StopCircle, X } from 'lucide-react';
import { Flag, Play, Send, Share } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '../../../lib/classname.ts';
import { useStickyStuck } from '../../../hooks/use-sticky-stuck.tsx';
import { StepperAction } from './StepperAction.tsx';
import { StepperStepSeparator } from './StepperStepSeparator.tsx';
import { MilestoneStep } from './MilestoneStep.tsx';
import { httpGet, httpPost } from '../../../lib/http.ts';
import { httpGet } from '../../../lib/http.ts';
import { StartProjectModal } from '../StartProjectModal.tsx';
import { getRelativeTimeString } from '../../../lib/date.ts';
import { getUser, isLoggedIn } from '../../../lib/jwt.ts';
@@ -13,7 +13,6 @@ import { showLoginPopup } from '../../../lib/popup.ts';
import { SubmitProjectModal } from '../SubmitProjectModal.tsx';
import { useCopyText } from '../../../hooks/use-copy-text.ts';
import { CheckIcon } from '../../ReactIcons/CheckIcon.tsx';
import { pageProgressMessage } from '../../../stores/page.ts';
type ProjectStatusResponse = {
id?: string;
@@ -41,8 +40,6 @@ export function ProjectStepper(props: ProjectStepperProps) {
const [isSubmittingProject, setIsSubmittingProject] = useState(false);
const { copyText, isCopied } = useCopyText();
const [isStoppingProject, setIsStoppingProject] = useState(false);
const [error, setError] = useState<string | null>(null);
const [activeStep, setActiveStep] = useState<number>(0);
const [isLoadingStatus, setIsLoadingStatus] = useState(true);
@@ -81,27 +78,6 @@ export function ProjectStepper(props: ProjectStepperProps) {
setIsLoadingStatus(false);
}
const stopProject = async () => {
setIsStoppingProject(true);
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-stop-project/${projectId}`,
{},
);
if (error || !response) {
setIsStoppingProject(false);
setError(error?.message || 'Error stopping project');
return;
}
pageProgressMessage.set('Update project status');
setActiveStep(0);
loadProjectStatus().finally(() => {
pageProgressMessage.set('');
setIsStoppingProject(false);
});
};
useEffect(() => {
loadProjectStatus().finally(() => {});
}, []);
@@ -213,20 +189,6 @@ export function ProjectStepper(props: ProjectStepperProps) {
</>
)}
{projectStatus?.startedAt && !projectStatus?.submittedAt && (
<button
className={cn(
'ml-auto hidden items-center gap-1.5 text-sm hover:text-black disabled:opacity-50 sm:flex',
)}
onClick={stopProject}
disabled={isStoppingProject}
>
<Square className="h-3 w-3 fill-current stroke-[2.5px]" />
<span className="hidden md:inline">Stop Working</span>
<span className="md:hidden">Stop</span>
</button>
)}
{activeStep >= 2 && (
<button
className={cn(
@@ -245,7 +207,7 @@ export function ProjectStepper(props: ProjectStepperProps) {
) : (
<>
<Share className="h-3 w-3 stroke-[2.5px]" />
<span className="hidden md:inline">Share your Solution</span>
<span className="hidden md:inline">Share Solution</span>
<span className="md:hidden">Share</span>
</>
)}
@@ -300,12 +262,12 @@ export function ProjectStepper(props: ProjectStepperProps) {
{isCopied ? (
<>
<CheckIcon additionalClasses="h-3 w-3" />
URL Copied
Copied
</>
) : (
<>
<Share className="h-3 w-3 stroke-[2.5px]" />
Share your Solution
Share Solution
</>
)}
</button>

View File

@@ -1,4 +1,5 @@
import { CheckIcon, CopyIcon, X } from 'lucide-react';
import { CheckIcon as ReactCheckIcon } from '../ReactIcons/CheckIcon.tsx';
import { Modal } from '../Modal';
import { type FormEvent, useState } from 'react';
import { httpPost } from '../../lib/http';
@@ -39,7 +40,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
const { isCopied, copyText } = useCopyText();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [isSuccess, setIsSuccess] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
const [repoUrl, setRepoUrl] = useState(defaultRepositoryUrl);
const [verificationChecks, setVerificationChecks] =
useState<VerificationChecksType>({
@@ -61,7 +62,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
setIsLoading(true);
setError('');
setIsSuccess(false);
setSuccessMessage('');
if (!repoUrl) {
setVerificationChecks({
@@ -198,7 +199,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
);
}
setIsSuccess(true);
setSuccessMessage('Solution submitted successfully!');
setIsLoading(false);
onSubmit(submitResponse);
@@ -209,8 +210,14 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
}
};
if (isSuccess) {
return <SubmitSuccessModal projectId={projectId} onClose={onClose} />;
if (successMessage) {
return (
<SubmitSuccessModal
projectId={projectId}
onClose={onClose}
successMessage={successMessage}
/>
);
}
return (
@@ -289,6 +296,12 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
{error && (
<p className="mt-2 text-sm font-medium text-red-500">{error}</p>
)}
{successMessage && (
<p className="mt-2 text-sm font-medium text-green-500">
{successMessage}
</p>
)}
</form>
<button

View File

@@ -1,4 +1,10 @@
import { CheckCircle, CheckCircle2, Clipboard, Copy } from 'lucide-react';
import {
CheckCircle2,
Clipboard,
Facebook,
Linkedin,
Twitter,
} from 'lucide-react';
import { getUser } from '../../lib/jwt.ts';
import { Modal } from '../Modal';
import { CheckIcon as ReactCheckIcon } from '../ReactIcons/CheckIcon.tsx';
@@ -8,43 +14,62 @@ import { cn } from '../../lib/classname.ts';
type SubmitSuccessModalProps = {
projectId: string;
onClose: () => void;
successMessage: string;
};
export function SubmitSuccessModal(props: SubmitSuccessModalProps) {
const { onClose, projectId } = props;
const { onClose, successMessage, projectId } = props;
const user = getUser();
const description = 'Check out my solution to this project on Roadmap.sh';
const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${user?.id}`;
const { isCopied, copyText } = useCopyText();
const socialShareLinks = [
{
title: 'Twitter',
href: `https://x.com/intent/tweet?text=${description}&url=${projectSolutionUrl}`,
icon: <Twitter className="size-4 text-gray-700" />,
},
{
title: 'Facebook',
href: `https://www.facebook.com/sharer/sharer.php?quote=${description}&u=${projectSolutionUrl}`,
icon: <Facebook className="size-4 text-gray-700" />,
},
{
title: 'Linkedin',
href: `https://www.linkedin.com/sharing/share-offsite/?url=${projectSolutionUrl}`,
icon: <Linkedin className="size-4 text-gray-700" />,
},
];
return (
<Modal onClose={onClose} bodyClassName="h-auto p-4">
<div className="flex flex-col items-center justify-center pb-3 pt-12">
<div className="flex flex-col items-center justify-center pb-5 pt-12">
<ReactCheckIcon additionalClasses="h-12 text-green-500 w-12" />
<p className="mt-4 text-lg font-medium">Solution Submitted</p>
<p className="mt-4 text-lg font-medium">{successMessage}</p>
<p className="mt-0.5 text-center text-sm text-gray-500">
Congrats! Your solution has been submitted.
You can use the link to share your solution with others.
</p>
<div className="mt-4 w-full">
<div className="mt-4 flex w-full items-stretch rounded-md border bg-gray-50">
<input
type="text"
readOnly={true}
value={projectSolutionUrl}
className="w-full rounded-md border bg-gray-50 px-2.5 py-2 text-sm text-gray-700 focus:outline-none"
className="w-full bg-transparent px-2.5 py-2 text-sm text-gray-700 focus:outline-none"
onClick={(e) => {
e.currentTarget.select();
copyText(projectSolutionUrl);
}}
/>
<button
className={cn(
'mt-2 flex w-full items-center justify-center gap-1 rounded-md px-2 py-2 text-sm font-medium transition-colors',
isCopied
? 'bg-green-600 text-white hover:bg-green-700'
: 'bg-black text-white hover:bg-gray-800'
'm-1 ml-0 flex items-center gap-1 rounded-md bg-gray-200 px-2 py-1.5 text-xs font-medium text-black',
isCopied ? 'bg-green-200 text-green-900' : '',
)}
onClick={() => {
copyText(projectSolutionUrl);
@@ -52,17 +77,35 @@ export function SubmitSuccessModal(props: SubmitSuccessModalProps) {
>
{isCopied ? (
<>
<CheckCircle className="size-4 stroke-[2.5px]" />
URL Copied
<CheckCircle2 className="size-3 stroke-[2.5px]" />
Copied
</>
) : (
<>
<Copy className="size-4 stroke-[2.5px]" />
Copy Shareable Link
<Clipboard className="size-3 stroke-[2.5px]" />
Copy
</>
)}
</button>
</div>
<div className="mt-8 flex justify-center gap-2">
{socialShareLinks.map((socialLink) => (
<a
key={socialLink.title}
href={socialLink.href}
target="_blank"
rel="noreferrer"
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md border bg-gray-50 hover:bg-gray-100"
>
{socialLink.icon}
</a>
))}
</div>
<p className="mt-4 text-sm text-gray-500">
Share your solution with the others!
</p>
</div>
</Modal>
);

View File

@@ -1,39 +0,0 @@
import type { SVGProps } from 'react';
import React from 'react';
export function BookEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path
fill="#3e721d"
d="M35 26a4 4 0 0 1-4 4H5a4 4 0 0 1-4-4V6.313C1 4.104 6.791 0 9 0h20.625C32.719 0 35 2.312 35 5.375z"
></path>
<path
fill="#ccd6dd"
d="M33 30a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V6c0-4.119-.021-4 5-4h21a4 4 0 0 1 4 4z"
></path>
<path
fill="#e1e8ed"
d="M31 31a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h24a3 3 0 0 1 3 3z"
></path>
<path
fill="#5c913b"
d="M31 32a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V10a4 4 0 0 1 4-4h21a4 4 0 0 1 4 4z"
></path>
<path
fill="#77b255"
d="M29 32a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4h19.335C27.544 8 29 9.456 29 11.665z"
></path>
<path
fill="#3e721d"
d="M6 6C4.312 6 4.269 4.078 5 3.25C5.832 2.309 7.125 2 9.438 2H11V0H8.281C4.312 0 1 2.5 1 5.375V32a4 4 0 0 0 4 4h2V6z"
></path>
</svg>
);
}

View File

@@ -1,36 +0,0 @@
import React from 'react';
import type { SVGProps } from 'react';
export function BuildEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path
fill="#66757f"
d="M28.25 8.513a.263.263 0 0 0-.263-.263h-.475a.263.263 0 0 0-.263.263v11.475c0 .145.117.263.263.263h.475a.263.263 0 0 0 .263-.263z"
></path>
<g fill="#f19020">
<circle cx={27.75} cy={19.75} r={1.5}></circle>
<circle cx={27.75} cy={22.25} r={1}></circle>
</g>
<path
fill="#bd2032"
d="M33.25 8.25h-4.129L9.946.29L9.944.289h-.001c-.016-.007-.032-.005-.047-.01C9.849.265 9.802.25 9.75.25h-.002a.5.5 0 0 0-.19.038a.5.5 0 0 0-.122.082c-.012.009-.026.014-.037.025a.5.5 0 0 0-.11.164V.56c-.004.009-.003.02-.006.029l-5.541 7.81l-.006.014a.99.99 0 0 0-.486.837v2a1 1 0 0 0 1 1h1.495L2.031 34H.25v2h18.958v-2h-1.74l-3.713-21.75H33.25a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1m-21.769 4L9.75 13.639L8.02 12.25zM9.75 21.3l3.667 2.404l-3.667 2l-3.667-2zm-3.639.71l.474-2.784l1.866 1.223zm4.938-1.561l1.87-1.225l.477 2.789zm-1.299-.866l-2.828-1.885l2.828-2.322l2.828 2.322zm-2.563-3.887l.362-2.127l1.131.928zm3.633-1.198l1.132-.929l.364 2.13zM5.073 8.25L9.25 2.362V6.25h-2a1 1 0 0 0-1 1v1zm.53 16.738l2.73 1.489l-3.29 1.794zM15.443 34H4.067l.686-4.024L9.75 27.25l5.006 2.731zm-1.54-9.015l.562 3.291l-3.298-1.799zM13.25 8.25v-1a1 1 0 0 0-1-1h-2V1.499L26.513 8.25zm2 3h-1.16v-2h1.16zm3 0h-2v-2h2zm3 0h-2v-2h2zm3 0h-2v-2h2zm3 0h-2v-2h2zm3 0h-2v-2h2zm3-.5a.5.5 0 0 1-.5.5h-1.5v-2h1.5a.5.5 0 0 1 .5.5z"
></path>
<path
fill="#4b545d"
d="M12.25 7.25h-2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h3v-4z"
></path>
<path fill="#cdd7df" d="M11.25 7.25h2v4h-2z"></path>
<path
fill="#66757f"
d="M34.844 24v-1H20.656v1h.844v2.469h-.844v1h14.188v-1H34V24z"
></path>
</svg>
);
}

View File

@@ -1,37 +0,0 @@
// twitter bulb emoji
import type { SVGProps } from 'react';
type BulbEmojiProps = SVGProps<SVGSVGElement>;
export function BulbEmoji(props: BulbEmojiProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path
fill="#FFD983"
d="M29 11.06c0 6.439-5 7.439-5 13.44c0 3.098-3.123 3.359-5.5 3.359c-2.053 0-6.586-.779-6.586-3.361C11.914 18.5 7 17.5 7 11.06C7 5.029 12.285.14 18.083.14C23.883.14 29 5.029 29 11.06"
></path>
<path
fill="#CCD6DD"
d="M22.167 32.5c0 .828-2.234 2.5-4.167 2.5s-4.167-1.672-4.167-2.5S16.066 32 18 32s4.167-.328 4.167.5"
></path>
<path
fill="#FFCC4D"
d="M22.707 10.293a1 1 0 0 0-1.414 0L18 13.586l-3.293-3.293a.999.999 0 1 0-1.414 1.414L17 15.414V26a1 1 0 1 0 2 0V15.414l3.707-3.707a1 1 0 0 0 0-1.414"
></path>
<path
fill="#99AAB5"
d="M24 31a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2v-6h12z"
></path>
<path
fill="#CCD6DD"
d="M11.999 32a1 1 0 0 1-.163-1.986l12-2a.994.994 0 0 1 1.15.822a1 1 0 0 1-.822 1.15l-12 2a1 1 0 0 1-.165.014m0-4a1 1 0 0 1-.163-1.986l12-2a.995.995 0 0 1 1.15.822a1 1 0 0 1-.822 1.15l-12 2a1 1 0 0 1-.165.014"
></path>
</svg>
);
}

View File

@@ -1,6 +0,0 @@
import React from 'react';
import type { SVGProps } from 'react';
export function CheckEmoji(props: SVGProps<SVGSVGElement>) {
return (<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 36 36" {...props}><path fill="#77b255" d="M36 32a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4h28a4 4 0 0 1 4 4z"></path><path fill="#fff" d="M29.28 6.362a2.5 2.5 0 0 0-3.458.736L14.936 23.877l-5.029-4.65a2.5 2.5 0 1 0-3.394 3.671l7.209 6.666c.48.445 1.09.665 1.696.665c.673 0 1.534-.282 2.099-1.139c.332-.506 12.5-19.27 12.5-19.27a2.5 2.5 0 0 0-.737-3.458"></path></svg>);
}

View File

@@ -1,24 +0,0 @@
import type { SVGProps } from 'react';
import React from 'react';
export function ConstructionEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path
fill="#ffcc4d"
d="M36 15a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h28a4 4 0 0 1 4 4z"
></path>
<path
fill="#292f33"
d="M6 3H4a4 4 0 0 0-4 4v2zm6 0L0 15c0 1.36.682 2.558 1.72 3.28L17 3zM7 19h5L28 3h-5zm16 0L35.892 6.108A4 4 0 0 0 33.64 3.36L18 19zm13-4v-3l-7 7h3a4 4 0 0 0 4-4"
></path>
<path fill="#99aab5" d="M4 19h5v14H4zm23 0h5v14h-5z"></path>
</svg>
);
}

View File

@@ -1,17 +0,0 @@
interface FacebookIconProps {
className?: string;
}
export function FacebookIcon(props: FacebookIconProps) {
const { className } = props;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill="currentColor"
className={className}
>
<path d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h137.25V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.27c-30.81 0-40.42 19.12-40.42 38.73V256h68.78l-11 71.69h-57.78V480H400a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48z" />
</svg>
);
}

View File

@@ -1,29 +1,49 @@
interface LinkedInIconProps {
type LinkedInIconProps = {
className?: string;
}
};
export function LinkedInIcon(props: LinkedInIconProps) {
const { className } = props;
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
x="0px"
y="0px"
width="100"
height="100"
viewBox="0,0,256,256"
>
<g clipPath="url(#clip0_2344_20)">
<path
d="M0 0V24H24V0H0ZM8 19H5V8H8V19ZM6.5 6.732C5.534 6.732 4.75 5.942 4.75 4.968C4.75 3.994 5.534 3.204 6.5 3.204C7.466 3.204 8.25 3.994 8.25 4.968C8.25 5.942 7.467 6.732 6.5 6.732ZM20 19H17V13.396C17 10.028 13 10.283 13 13.396V19H10V8H13V9.765C14.397 7.179 20 6.988 20 12.241V19Z"
fill="currentColor"
/>
<g transform="translate(-26.66667,-26.66667) scale(1.20833,1.20833)">
<g
fill="none"
fillRule="nonzero"
stroke="none"
strokeWidth="1"
strokeLinecap="butt"
strokeLinejoin="miter"
strokeMiterlimit="10"
strokeDasharray=""
strokeDashoffset="0"
fontFamily="none"
fontWeight="none"
fontSize="none"
textAnchor="none"
style={{ mixBlendMode: 'normal' }}
>
<g transform="scale(5.33333,5.33333)">
<path
d="M42,37c0,2.762 -2.238,5 -5,5h-26c-2.761,0 -5,-2.238 -5,-5v-26c0,-2.762 2.239,-5 5,-5h26c2.762,0 5,2.238 5,5z"
fill="#0288d1"
></path>
<path
d="M12,19h5v17h-5zM14.485,17h-0.028c-1.492,0 -2.457,-1.112 -2.457,-2.501c0,-1.419 0.995,-2.499 2.514,-2.499c1.521,0 2.458,1.08 2.486,2.499c0,1.388 -0.965,2.501 -2.515,2.501zM36,36h-5v-9.099c0,-2.198 -1.225,-3.698 -3.192,-3.698c-1.501,0 -2.313,1.012 -2.707,1.99c-0.144,0.35 -0.101,1.318 -0.101,1.807v9h-5v-17h5v2.616c0.721,-1.116 1.85,-2.616 4.738,-2.616c3.578,0 6.261,2.25 6.261,7.274l0.001,9.726z"
fill="#ffffff"
></path>
</g>
</g>
</g>
<defs>
<clipPath id="clip0_2344_20">
<rect width="24" height="24" rx="2" fill="white" />
</clipPath>
</defs>
</svg>
);
}

View File

@@ -1,19 +0,0 @@
import type { SVGProps } from 'react';
export function RankBadgeIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
width="11"
height="11"
viewBox="0 0 11 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M0 0L11 0V10.0442L5.73392 6.32786L0 10.0442L0 0Z"
fill="currentColor"
></path>
</svg>
);
}

View File

@@ -1,25 +0,0 @@
import React from 'react';
import type { SVGProps } from 'react';
export function SecondPlaceMedalEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path fill="#55acee" d="m18 8l-7-8H0l14 17l11.521-4.75z"></path>
<path fill="#3b88c3" d="m25 0l-7 8l5.39 7.312l1.227-1.489L36 0z"></path>
<path
fill="#ccd6dd"
d="M23.205 16.026c.08-.217.131-.448.131-.693a2 2 0 0 0-2-2h-6.667a2 2 0 0 0-2 2c0 .245.05.476.131.693c-3.258 1.826-5.464 5.307-5.464 9.307C7.335 31.224 12.111 36 18.002 36s10.667-4.776 10.667-10.667c0-4-2.206-7.481-5.464-9.307"
></path>
<path
fill="#627077"
d="M22.002 28.921h-3.543c.878-1.234 2.412-3.234 3.01-4.301c.449-.879.729-1.439.729-2.43c0-2.076-1.57-3.777-4.244-3.777c-2.225 0-3.74 1.832-3.74 1.832c-.131.15-.112.374.019.487l1.141 1.159a.36.36 0 0 0 .523 0c.355-.393 1.047-.935 1.813-.935c1.047 0 1.646.635 1.646 1.346c0 .523-.243 1.047-.486 1.421c-1.104 1.682-3.871 5.441-4.955 6.862v.374c0 .188.149.355.355.355h7.732a.37.37 0 0 0 .355-.355v-1.682a.367.367 0 0 0-.355-.356"
></path>
</svg>
);
}

View File

@@ -1,6 +1,6 @@
import type { SVGAttributes } from 'react';
import type { JSX } from "preact/jsx-runtime";
type ShareIconProps = SVGAttributes<SVGSVGElement>;
type ShareIconProps = JSX.SVGAttributes<SVGSVGElement>
export function ShareIcon(props: ShareIconProps) {
return (

View File

@@ -1,25 +0,0 @@
import React from 'react';
import type { SVGProps } from 'react';
export function ThirdPlaceMedalEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path fill="#55ACEE" d="m18 8l-7-8H0l14 17l11.521-4.75z"></path>
<path fill="#3B88C3" d="m25 0l-7 8l5.39 7.312l1.227-1.489L36 0z"></path>
<path
fill="#FF8A3B"
d="M23.205 16.026c.08-.217.131-.448.131-.693a2 2 0 0 0-2-2h-6.667a2 2 0 0 0-2 2c0 .245.05.476.131.693c-3.258 1.826-5.464 5.307-5.464 9.307C7.335 31.224 12.111 36 18.002 36s10.667-4.776 10.667-10.667c0-4-2.206-7.481-5.464-9.307"
></path>
<path
fill="#7C4119"
d="m14.121 29.35l1.178-1.178a.345.345 0 0 1 .467-.038s1.159.861 2.056.861c.805 0 1.628-.673 1.628-1.496s-.842-1.514-2.225-1.514h-.639a.367.367 0 0 1-.354-.355v-1.552c0-.206.168-.355.354-.355h.639c1.309 0 2-.635 2-1.439c0-.805-.691-1.402-1.496-1.402c-.823 0-1.346.43-1.626.747c-.132.15-.355.15-.504.02l-1.141-1.122c-.151-.132-.132-.355 0-.486c0 0 1.533-1.646 3.57-1.646c2.169 0 4.039 1.328 4.039 3.422c0 1.439-1.085 2.505-1.926 2.897v.057c.879.374 2.262 1.533 2.262 3.141c0 2.038-1.776 3.572-4.357 3.572c-2.354 0-3.552-1.16-3.944-1.664c-.113-.134-.093-.34.019-.47"
></path>
</svg>
);
}

View File

@@ -1,31 +0,0 @@
import React from 'react';
import type { SVGProps } from 'react';
export function TrophyEmoji(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 36 36"
{...props}
>
<path
fill="#ffac33"
d="M5.123 5h6C12.227 5 13 4.896 13 6V4c0-1.104-.773-2-1.877-2h-8c-2 0-3.583 2.125-3 5c0 0 1.791 9.375 1.917 9.958C2.373 18.5 4.164 20 6.081 20h6.958c1.105 0-.039-1.896-.039-3v-2c0 1.104-.773 2-1.877 2h-4c-1.104 0-1.833-1.042-2-2S3.539 7.667 3.539 7.667C3.206 5.75 4.018 5 5.123 5m25.812 0h-6C23.831 5 22 4.896 22 6V4c0-1.104 1.831-2 2.935-2h8c2 0 3.584 2.125 3 5c0 0-1.633 9.419-1.771 10c-.354 1.5-2.042 3-4 3h-7.146C21.914 20 22 18.104 22 17v-2c0 1.104 1.831 2 2.935 2h4c1.104 0 1.834-1.042 2-2s1.584-7.333 1.584-7.333C32.851 5.75 32.04 5 30.935 5M20.832 22c0-6.958-2.709 0-2.709 0s-3-6.958-3 0s-3.291 10-3.291 10h12.292c-.001 0-3.292-3.042-3.292-10"
></path>
<path
fill="#ffcc4d"
d="M29.123 6.577c0 6.775-6.77 18.192-11 18.192s-11-11.417-11-18.192c0-5.195 1-6.319 3-6.319c1.374 0 6.025-.027 8-.027l7-.001c2.917-.001 4 .684 4 6.347"
></path>
<path
fill="#c1694f"
d="M27 33c0 1.104.227 2-.877 2h-16C9.018 35 9 34.104 9 33v-1c0-1.104 1.164-2 2.206-2h13.917c1.042 0 1.877.896 1.877 2z"
></path>
<path
fill="#c1694f"
d="M29 34.625c0 .76.165 1.375-1.252 1.375H8.498C7.206 36 7 35.385 7 34.625v-.25C7 33.615 7.738 33 8.498 33h19.25c.759 0 1.252.615 1.252 1.375z"
></path>
</svg>
);
}

View File

@@ -1,24 +1,22 @@
interface TwitterIconProps {
type TwitterIconProps = {
className?: string;
boxColor?: string;
}
};
export function TwitterIcon(props: TwitterIconProps) {
const { className, boxColor = 'transparent' } = props;
const { className } = props;
return (
<svg
width="23"
height="23"
viewBox="0 0 23 23"
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="23" height="23" rx="3" fill={boxColor} />
<path
d="M12.9285 10.3522L18.5135 4H17.1905L12.339 9.5144L8.467 4H4L9.8565 12.3395L4 19H5.323L10.443 13.1754L14.533 19H19M5.8005 4.97619H7.833L17.1895 18.0718H15.1565"
fill='currentColor'
d="M8.9285 6.35221L14.5135 0H13.1905L8.339 5.5144L4.467 0H0L5.8565 8.33955L0 15H1.323L6.443 9.17535L10.533 15H15M1.8005 0.976187H3.833L13.1895 14.0718H11.1565"
fill="currentColor"
/>
</svg>
);

View File

@@ -3,11 +3,6 @@ import { cn } from '../../lib/classname.ts';
import { Filter, X } from 'lucide-react';
import { CategoryFilterButton } from './CategoryFilterButton.tsx';
import { useOutsideClick } from '../../hooks/use-outside-click.ts';
import {
deleteUrlParam,
getUrlParams,
setUrlParams,
} from '../../lib/browser.ts';
const groupNames = [
'Absolute Beginners',
@@ -473,15 +468,6 @@ export function RoadmapsPage() {
]);
}, [activeGroup]);
useEffect(() => {
const { g } = getUrlParams() as { g: AllowGroupNames };
if (!g) {
return;
}
setActiveGroup(g);
}, []);
return (
<div className="border-t bg-gray-100">
<button
@@ -516,7 +502,6 @@ export function RoadmapsPage() {
onClick={() => {
setActiveGroup('');
setIsFilterOpen(false);
deleteUrlParam('g');
}}
category={'All Roadmaps'}
selected={activeGroup === ''}
@@ -529,7 +514,6 @@ export function RoadmapsPage() {
setActiveGroup(group.group);
setIsFilterOpen(false);
document?.getElementById('filter-button')?.scrollIntoView();
setUrlParams({ g: group.group });
}}
category={group.group}
selected={activeGroup === group.group}

View File

@@ -70,7 +70,7 @@ export function ShareRoadmapButton(props: ShareRoadmapButtonProps) {
</button>
{isDropdownOpen && (
<div className="absolute right-0 z-[999] mt-1 w-40 rounded-md bg-slate-800 text-sm text-white shadow-lg ring-1 ring-black ring-opacity-5 w-[175px]">
<div className="absolute right-0 z-[999] mt-1 w-40 rounded-md bg-slate-800 text-sm text-white shadow-lg ring-1 ring-black ring-opacity-5">
<div className="flex flex-col px-1 py-1">
<button
onClick={() => {
@@ -103,7 +103,7 @@ export function ShareRoadmapButton(props: ShareRoadmapButtonProps) {
target={'_blank'}
className="mt-1 flex w-full items-center gap-2 rounded-sm px-2 py-2 text-sm text-slate-100 hover:bg-slate-700"
>
<div className="flex w-[20px] flex-shrink-0 items-center justify-center">
<div className="flex w-[20px] items-center justify-center">
<TwitterIcon className="h-[16px] text-slate-400" />
</div>
Twitter

View File

@@ -49,13 +49,8 @@ type GetTeamActivityResponse = {
perPage: number;
};
type TeamActivityPageProps = {
teamId?: string;
};
export function TeamActivityPage(props: TeamActivityPageProps) {
const { teamId: defaultTeamId } = props;
const { t: teamId = defaultTeamId } = getUrlParams();
export function TeamActivityPage() {
const { t: teamId } = getUrlParams();
const toast = useToast();
@@ -97,18 +92,6 @@ export function TeamActivityPage(props: TeamActivityPageProps) {
return;
}
setIsLoading(true);
setTeamActivities({
data: {
users: [],
activities: [],
},
totalCount: 0,
totalPages: 0,
currPage: 1,
perPage: 21,
});
setCurrPage(1);
getTeamProgress().then(() => {
pageProgressMessage.set('');
setIsLoading(false);

View File

@@ -91,7 +91,6 @@ export function TeamPricing() {
{
'top-full': !isCopied,
'top-0': isCopied,
'opacity-0': !isCopied,
},
)}
>

View File

@@ -12,7 +12,6 @@ type TeamMemberProps = {
userId: string;
index: number;
teamId: string;
canViewProgress: boolean;
canManageCurrentTeam: boolean;
onDeleteMember: () => void;
onUpdateMember: () => void;
@@ -30,12 +29,11 @@ export function TeamMemberItem(props: TeamMemberProps) {
userId,
onDeleteMember,
onSendProgressReminder,
canViewProgress = true,
} = props;
const currentTeam = useStore($currentTeam);
const canManageTeam = useStore($canManageCurrentTeam);
const showNoProgressBadge = canViewProgress && !member.hasProgress && member.status === 'joined';
const showNoProgressBadge = !member.hasProgress && member.status === 'joined';
const allowProgressReminder =
canManageTeam &&
!member.hasProgress &&

View File

@@ -61,7 +61,7 @@ export function TeamMembersPage() {
async function loadTeam() {
const { response, error } = await httpGet<TeamDocument>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}`,
`${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamId}`
);
if (error || !response) {
toast.error(error?.message || 'Something went wrong');
@@ -75,7 +75,7 @@ export function TeamMembersPage() {
async function getTeamMemberList() {
const { response, error } = await httpGet<TeamMemberItem[]>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-member-list/${teamId}`,
`${import.meta.env.PUBLIC_API_URL}/v1-get-team-member-list/${teamId}`
);
if (error || !response) {
toast.error(error?.message || 'Failed to load team member list');
@@ -100,7 +100,7 @@ export function TeamMembersPage() {
`${
import.meta.env.PUBLIC_API_URL
}/v1-delete-member/${teamId}/${memberId}`,
{},
{}
);
if (error || !response) {
@@ -118,7 +118,7 @@ export function TeamMembersPage() {
`${
import.meta.env.PUBLIC_API_URL
}/v1-resend-invite/${teamId}/${memberId}`,
{},
{}
);
if (error || !response) {
@@ -135,7 +135,7 @@ export function TeamMembersPage() {
`${
import.meta.env.PUBLIC_API_URL
}/v1-send-progress-reminder/${teamId}/${memberId}`,
{},
{}
);
if (error || !response) {
@@ -147,13 +147,13 @@ export function TeamMembersPage() {
}
const joinedMembers = teamMembers.filter(
(member) => member.status === 'joined',
(member) => member.status === 'joined'
);
const invitedMembers = teamMembers.filter(
(member) => member.status === 'invited',
(member) => member.status === 'invited'
);
const rejectedMembers = teamMembers.filter(
(member) => member.status === 'rejected',
(member) => member.status === 'rejected'
);
return (
@@ -205,11 +205,6 @@ export function TeamMembersPage() {
index={index}
teamId={teamId}
userId={user?.id!}
canViewProgress={
canManageCurrentTeam ||
!team?.personalProgressOnly ||
String(member.userId) === user?.id
}
onResendInvite={() => {
resendInvite(teamId, member._id!).finally(() => {
pageProgressMessage.set('');
@@ -246,7 +241,6 @@ export function TeamMembersPage() {
index={index}
teamId={teamId}
userId={user?.id!}
canViewProgress={false}
onResendInvite={() => {
resendInvite(teamId, member._id!).finally(() => {
pageProgressMessage.set('');
@@ -275,9 +269,7 @@ export function TeamMembersPage() {
{rejectedMembers.length > 0 && (
<div className="mt-6">
<h3 className="text-xs uppercase text-gray-400">
Rejected Invites
</h3>
<h3 className="text-xs uppercase text-gray-400">Rejected Invites</h3>
<div className="mt-2 rounded-b-sm rounded-t-md border">
{rejectedMembers.map((member, index) => {
return (
@@ -286,7 +278,6 @@ export function TeamMembersPage() {
member={member}
index={index}
teamId={teamId}
canViewProgress={false}
userId={user?.id!}
onResendInvite={() => {
resendInvite(teamId, member._id!).finally(() => {

View File

@@ -10,7 +10,6 @@ import { getUrlParams, setUrlParams } from '../../lib/browser';
import { useAuth } from '../../hooks/use-auth';
import { MemberProgressModal } from './MemberProgressModal';
import { MemberCustomProgressModal } from './MemberCustomProgressModal';
import { canManageCurrentRoadmap } from '../../stores/roadmap.ts';
export type UserProgress = {
resourceTitle: string;
@@ -24,7 +23,6 @@ export type UserProgress = {
updatedAt: string;
isCustomResource?: boolean;
roadmapSlug?: string;
aiRoadmapId?: string;
};
export type TeamMember = {
@@ -193,7 +191,7 @@ export function TeamProgressPage() {
key={grouping.value}
className={`rounded-md border p-1 px-2 text-sm ${
selectedGrouping === grouping.value
? 'border-gray-400 bg-gray-200'
? ' border-gray-400 bg-gray-200 '
: ''
}`}
onClick={() => setSelectedGrouping(grouping.value)}
@@ -225,32 +223,21 @@ export function TeamProgressPage() {
)}
{selectedGrouping === 'member' && (
<div className="grid gap-4 sm:grid-cols-2">
{teamMembers.map((member) => {
const canViewMemberProgress =
currentTeam?.role !== 'member' ||
!currentTeam?.personalProgressOnly ||
member.email === user?.email;
if (!canViewMemberProgress) {
return null;
}
return (
<MemberProgressItem
key={member._id}
member={member}
teamId={teamId}
isMyProgress={member?.email === user?.email}
onShowResourceProgress={(resourceId, isCustomResource) => {
setShowMemberProgress({
resourceId,
member,
isCustomResource,
});
}}
/>
);
})}
{teamMembers.map((member) => (
<MemberProgressItem
key={member._id}
member={member}
teamId={teamId}
isMyProgress={member?.email === user?.email}
onShowResourceProgress={(resourceId, isCustomResource) => {
setShowMemberProgress({
resourceId,
member,
isCustomResource,
});
}}
/>
))}
</div>
)}
</div>

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