mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 02:01:57 +08:00
Compare commits
1 Commits
fix/dashbo
...
feat/resou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2440f31ec |
@@ -3,6 +3,6 @@
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1725962974592
|
||||
"lastUpdateCheck": 1723855511353
|
||||
}
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="astro/client" />
|
||||
16
.github/workflows/cloudfront-api-cache.yml
vendored
16
.github/workflows/cloudfront-api-cache.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Clears API Cloudfront Cache
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
aws_costs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clear Cloudfront Caching
|
||||
run: |
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
|
||||
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront-api", "is_verbose": false } }'
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Clears Frontend Cloudfront Cache
|
||||
name: Clears Cloudfront Cache
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
35
.github/workflows/deployment.yml
vendored
35
.github/workflows/deployment.yml
vendored
@@ -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 \
|
||||
|
||||
10
.github/workflows/label-issue.yml
vendored
10
.github/workflows/label-issue.yml
vendored
@@ -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,
|
||||
|
||||
@@ -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
38
.github/workflows/update-deps.yml
vendored
Normal 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.
|
||||
51
.github/workflows/upgrade-dependencies.yml
vendored
51
.github/workflows/upgrade-dependencies.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Upgrade Dependencies
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
upgrade-deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js Version 20 (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup pnpm@v9
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install & Upgrade Dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
npm run upgrade
|
||||
pnpm install --lockfile-only
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
delete-branch: false
|
||||
branch: "update-deps"
|
||||
base: "master"
|
||||
labels: |
|
||||
dependencies
|
||||
automated pr
|
||||
reviewers: kamranahmedse
|
||||
commit-message: "chore: update dependencies to latest"
|
||||
title: "Upgrade Dependencies To Latest - Automated"
|
||||
body: |
|
||||
## Updated all Dependencies to Latest Versions.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This PR Upgrades the Dependencies to the Latest Their Versions.
|
||||
>
|
||||
> Commit: ${{ github.sha }}
|
||||
> Workflow Path: ${{ github.workflow_ref }}
|
||||
|
||||
**Please Review the Changes and Merge the PR if everything is fine.**
|
||||
@@ -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
533
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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
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 |
@@ -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, it’s 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",
|
||||
@@ -266,14 +237,8 @@
|
||||
},
|
||||
"boMz0HZlMAsLdCZlpUo-H": {
|
||||
"title": "EditText",
|
||||
"description": "`EditText` is a fundamental UI element in Android Studio, used for allowing users to input and edit text within an application. It is a subclass of `TextView` that provides additional features to handle user input.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Android developers: EditText",
|
||||
"url": "https://developer.android.com/reference/android/widget/EditText",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Mtx0bY0drmaTw8sCM5YTl": {
|
||||
"title": "Dialogs",
|
||||
@@ -310,79 +275,33 @@
|
||||
},
|
||||
"A4rtNULX_MoV93IH1Lgqw": {
|
||||
"title": "ImageView",
|
||||
"description": "Displays image resources, for example Bitmap or Drawable resources. ImageView is also commonly used to apply tints to an image and handle image scaling.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Android developers: ImageView",
|
||||
"url": "https://developer.android.com/reference/android/widget/ImageView",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Z4Tbd5ClnqCXGPGG09F-G": {
|
||||
"title": "Bottom Sheet",
|
||||
"description": "`Bottom sheets` are surfaces containing supplementary content that are anchored to the bottom of the screen.\n\nThere are several attributes that can be used to adjust the behavior of both standard and modal bottom sheets. Behavior attributes can be applied to standard bottom sheets in xml by setting them on a child View set to `app:layout_behavior` or programmatically.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Android developers: Bottom sheets",
|
||||
"url": "https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetDialog",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"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`. It’s 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",
|
||||
"description": "Tabs in Android Studio are a UI component used to organize content into multiple sections, allowing users to navigate between them by selecting the corresponding tab. This component is commonly used when there is a need to present different types of content in a single screen, like different categories, settings, or pages within an app.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Android developers: Material Tabs",
|
||||
"url": "https://developer.android.com/reference/com/google/android/material/tabs/package-summary",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"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 +331,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",
|
||||
@@ -620,19 +533,8 @@
|
||||
},
|
||||
"e3vHFaFFMV7kI9q6yf5e9": {
|
||||
"title": "Cloud Messaging",
|
||||
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Documentation",
|
||||
"url": "https://firebase.google.com/docs/cloud-messaging/android/client",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Firebase Cloud Messaging",
|
||||
"url": "https://www.youtube.com/watch?v=sioEY4tWmLI&list=PLl-K7zZEsYLkuHRCtHTpi6JYHka8oHLft",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.",
|
||||
"links": []
|
||||
},
|
||||
"3EEfKAd-ppIQpdQSEhbA1": {
|
||||
"title": "FireStore",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -798,7 +798,7 @@
|
||||
"description": "Stoplight is an advanced tool that offers a comprehensive platform for technical teams to handle all aspects of API design. Leveraging Stoplight, teams can design, document and develop APIs in a more collaborative and streamlined manner. It uses an OpenAPI specification and allows users to design APIs visually, making API development easier. With its ability to auto-generate API documentation, performing API mock testing, and providing API management features, Stoplight plays a crucial role in adopting a design-first approach in API development. By using Stoplight, APIs can be designed to be easy-to-use, scalable, and robust from the outset, which ultimately improves the overall development process and quality of the APIs.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "stoplightio",
|
||||
"title": "/stoplightio",
|
||||
"url": "https://github.com/stoplightio",
|
||||
"type": "opensource"
|
||||
},
|
||||
@@ -1259,7 +1259,7 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What is API mocking (What is API Mocking? Definition, Guide, and Best Practices)",
|
||||
"title": "@articleWhat is API mocking (What is API Mocking? Definition, Guide, and Best Practices)",
|
||||
"url": "https://blog.postman.com/what-is-api-mocking/",
|
||||
"type": "article"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"3xp2fogAVmwXQhdzhZDWR": {
|
||||
"title": "Introduction",
|
||||
"description": "Data Analysis plays a crucial role in today's data-centric world. It involves the practice of inspecting, cleansing, transforming, and modeling data to extract valuable insights for decision-making. A **Data Analyst** is a professional primarily tasked with collecting, processing, and performing statistical analysis on large datasets. They discover how data can be used to answer questions and solve problems. With the rapid expansion of data in modern firms, the role of a data analyst has been evolving greatly, making them a significant asset in business strategy and decision-making processes.",
|
||||
"description": "Data Analysis plays a crucial role in today's data-centric world. It involves the practice of inspecting, cleansing, transforming, and modeling data to extract valuable insights for decision-making. A **Data Analyst** is a professional primarily tasked with collecting, processing, and performing statistical analysis on large datasets. They discover how data can be used to answer questions and solve problems. With the rapid expansion of data in modern firms, the role of a data analyst has been evolving greatly, making them a significant asset in business strategy and decision-making processes.\n\nLearn more from the following resources:",
|
||||
"links": []
|
||||
},
|
||||
"yCnn-NfSxIybUQ2iTuUGq": {
|
||||
@@ -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"
|
||||
}
|
||||
@@ -204,11 +183,6 @@
|
||||
"title": "Analaysis / Reporting with Excel",
|
||||
"description": "Excel is a powerful tool utilized by data analysts worldwide to store, manipulate, and analyze data. It offers a vast array of features such as pivot tables, graphs and a powerful suite of formulas and functions to help sift through large sets of data. A data analyst uses Excel to perform a wide range of tasks, from simple data entry and cleaning, to more complex statistical analysis and predictive modeling. Proficiency in Excel is often a key requirement for a data analyst, as its versatility and ubiquity make it an indispensable tool in the field of data analysis.",
|
||||
"links": [
|
||||
{
|
||||
"title": "Microsoft Excel Course",
|
||||
"url": "https://support.microsoft.com/en-us/office/excel-video-training-9bc05390-e94c-46af-a5b3-d7c22f6990bb",
|
||||
"type": "course"
|
||||
},
|
||||
{
|
||||
"title": "W3Schools - Excel",
|
||||
"url": "https://www.w3schools.com/excel/index.php",
|
||||
@@ -240,11 +214,6 @@
|
||||
"title": "DATEDIF function",
|
||||
"url": "https://support.microsoft.com/en-gb/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "How to use DATEDIF in Excel",
|
||||
"url": "https://www.excel-easy.com/examples/datedif.html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -434,16 +403,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"i4VCwFm-wc9cqE73i-BIb": {
|
||||
"title": "Learn SQL",
|
||||
"description": "Structured Query Language, or SQL, is an essential tool for every data analyst. As a domain-specific language used in programming and designed for managing data held in relational database management systems, SQL allows analysts to manipulate and analyse large volumes of data efficiently. Understanding SQL allows a data analyst to extract insights from data stored in databases, conduct complex queries, and create elaborate data reports. SQL is recognized for its effectiveness in data manipulation and its compatibility with other coding languages, making it a fundamental competency in the data analytics field.",
|
||||
"links": []
|
||||
},
|
||||
"i2uEcaO4bJhcZ5ayRs2CQ": {
|
||||
"title": "Learn a Programming Lang.",
|
||||
"description": "We have two main programming languages when it comes to data analysis: Python and R. Both have extensive libraries to help with decision-making processes in various situations, assisting in manipulating, modeling, and visualizing data. Python is a versatile language, used not only for data analysis but also for web development, automation, artificial intelligence, and more. R, on the other hand, was specifically created for statistical analysis and data visualization, making it an excellent choice for statisticians and researchers. It is known for its advanced visualization capabilities, allowing the creation of highly customizable and sophisticated graphs and plots.\n\nWith potential doubts about which language to choose to advance in a data career, it is ideal to consider your goals and/or the current market needs and choose which language to learn. If you are more interested in a career that combines data analysis with software development, automation, or artificial intelligence, Python may be the best choice. If your focus is purely on statistics and data visualization, R might be more suitable.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Python Data Science Handbook",
|
||||
"url": "https://jakevdp.github.io/PythonDataScienceHandbook/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "We have two main programming languages when it comes to data analysis: Python and R. Both have extensive libraries to help with decision-making processes in various situations, assisting in manipulating, modeling, and visualizing data. Python is a versatile language, used not only for data analysis but also for web development, automation, artificial intelligence, and more. R, on the other hand, was specifically created for statistical analysis and data visualization, making it an excellent choice for statisticians and researchers. It is known for its advanced visualization capabilities, allowing the creation of highly customizable and sophisticated graphs and plots.\n\nWith potential doubts about which language to choose to advance in a data career, it is ideal to consider your goals and/or the current market needs and choose which language to learn. If you are more interested in a career that combines data analysis with software development, automation, or artificial intelligence, Python may be the best choice. If your focus is purely on statistics and data visualization, R might be more suitable.",
|
||||
"links": []
|
||||
},
|
||||
"g_EBQizZsIe-vn8ir6FTv": {
|
||||
"title": "R",
|
||||
@@ -523,11 +491,6 @@
|
||||
"title": "Ggplot2",
|
||||
"description": "When it comes to data visualization in R programming, ggplot2 stands tall as one of the primary tools for data analysts. This data visualization library, which forms part of the tidyverse suite of packages, facilitates the creation of complex and sophisticated visual narratives. With its grammar of graphics philosophy, ggplot2 enables analysts to build graphs and charts layer by layer, thereby offering detailed control over graphical features and design. Its versatility in creating tailored and aesthetically pleasing graphics is a vital asset for any data analyst tackling exploratory data analysis, reporting, or dashboard building.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "ggplot2 website",
|
||||
"url": "https://ggplot2.tidyverse.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Make beautiful graphs in R",
|
||||
"url": "https://www.youtube.com/watch?v=qnw1xDnt_Ec",
|
||||
@@ -656,11 +619,6 @@
|
||||
"title": "Data Transformation",
|
||||
"description": "Data Transformation, also known as Data Wrangling, is an essential part of a Data Analyst's role. This process involves the conversion of data from a raw format into another format to make it more appropriate and valuable for a variety of downstream purposes such as analytics. Data Analysts transform data to make the data more suitable for analysis, ensure accuracy, and to improve data quality. The right transformation techniques can give the data a structure, multiply its value, and enhance the accuracy of the analytics performed by serving meaningful results.",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is data transformation?",
|
||||
"url": "https://www.qlik.com/us/data-management/data-transformation",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Explore top posts about Data Analysis",
|
||||
"url": "https://app.daily.dev/tags/data-analysis?ref=roadmapsh",
|
||||
@@ -721,14 +679,9 @@
|
||||
"description": "Dispersion in descriptive analysis, specifically for a data analyst, offers a crucial way to understand the variability or spread in a set of data. Descriptive analysis focus on describing and summarizing data to find patterns, relationships, or trends. Distinct measures of dispersion such as range, variance, standard deviation, and interquartile range gives data analysts insight into how spread out data points are, and how reliable any patterns detected may be. This understanding of dispersion helps data analysts in identifying outliers, drawing meaningful conclusions, and making informed predictions.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is dispersion?",
|
||||
"url": "https://www.investopedia.com/terms/d/dispersion.asp",
|
||||
"title": "Standard Deviation and Variance",
|
||||
"url": "https://www.mathsisfun.com/data/standard-deviation.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Statistics 101 - Measures of Dispersion",
|
||||
"url": "https://www.youtube.com/watch?v=goXdWMZxlqM",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -798,19 +751,8 @@
|
||||
},
|
||||
"yn1sstYMO9du3rpfQqNs9": {
|
||||
"title": "Average",
|
||||
"description": "When focusing on data analysis, understanding key statistical concepts is crucial. Amongst these, central tendency is a foundational element. Central Tendency refers to the measure that determines the center of a distribution. The average is a commonly used statistical tool by which data analysts discern trends and patterns. As one of the most recognized forms of central tendency, figuring out the \"average\" involves summing all values in a data set and dividing by the number of values. This provides analysts with a 'typical' value, around which the remaining data tends to cluster, facilitating better decision-making based on existing data.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "How to calculate the average",
|
||||
"url": "https://support.microsoft.com/en-gb/office/calculate-the-average-of-a-group-of-numbers-e158ef61-421c-4839-8290-34d7b1e68283#:~:text=Average%20This%20is%20the%20arithmetic,by%206%2C%20which%20is%205.",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Average Formula",
|
||||
"url": "https://www.cuemath.com/average-formula/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "When focusing on data analysis, understanding key statistical concepts is crucial. Amongst these, central tendency is a foundational element. Central Tendency refers to the measure that determines the center of a distribution. The average is a commonly used statistical tool by which data analysts discern trends and patterns. As one of the most recognized forms of central tendency, figuring out the \"average\" involves summing all values in a data set and dividing by the number of values. This provides analysts with a 'typical' value, around which the remaining data tends to cluster, facilitating better decision-making based on existing data.",
|
||||
"links": []
|
||||
},
|
||||
"tSxtyJhL5wjU0XJcjsJmm": {
|
||||
"title": "Range",
|
||||
@@ -828,12 +770,12 @@
|
||||
"description": "Data analysts heavily rely on statistical concepts to analyze and interpret data, and one such fundamental concept is variance. Variance, an essential measure of dispersion, quantifies the spread of data, providing insight into the level of variability within the dataset. Understanding variance is crucial for data analysts as the reliability of many statistical models depends on the assumption of constant variance across observations. In other words, it helps analysts determine how much data points diverge from the expected value or mean, which can be pivotal in identifying outliers, understanding data distribution, and driving decision-making processes. However, variance can't be interpreted in the original units of measurement due to its squared nature, which is why it is often used in conjunction with its square root, the standard deviation.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is variance?",
|
||||
"title": "",
|
||||
"url": "https://www.investopedia.com/terms/v/variance.asp",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "How to calculate variance",
|
||||
"title": "https://www.scribbr.co.uk/stats/variance-meaning/",
|
||||
"url": "https://www.scribbr.co.uk/stats/variance-meaning/",
|
||||
"type": "article"
|
||||
}
|
||||
@@ -874,11 +816,6 @@
|
||||
"title": "Kurtosis: Definition, Types, and Importance",
|
||||
"url": "https://www.investopedia.com/terms/k/kurtosis.asp",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What is Kurtosis?",
|
||||
"url": "https://www.youtube.com/watch?v=AsxEDBhESJg",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -932,19 +869,8 @@
|
||||
},
|
||||
"tvDdXwaRPsUSTqJGaLS3P": {
|
||||
"title": "Matplotlib",
|
||||
"description": "For a Data Analyst, understanding data and being able to represent it in a visually insightful form is a crucial part of effective decision-making in any organization. Matplotlib, a plotting library for the Python programming language, is an extremely useful tool for this purpose. It presents a versatile framework for generating line plots, scatter plots, histogram, bar charts and much more in a very straightforward manner. This library also allows for comprehensive customizations, offering a high level of control over the look and feel of the graphics it produces, which ultimately enhances the quality of data interpretation and communication.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Matplotlib Website",
|
||||
"url": "https://matplotlib.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Learn Matplotlib in 6 minutes",
|
||||
"url": "https://www.youtube.com/watch?v=nzKy9GY12yo",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "For a Data Analyst, understanding data and being able to represent it in a visually insightful form is a crucial part of effective decision-making in any organization. Matplotlib, a plotting library for the Python programming language, is an extremely useful tool for this purpose. It presents a versatile framework for generating line plots, scatter plots, histogram, bar charts and much more in a very straightforward manner. This library also allows for comprehensive customizations, offering a high level of control over the look and feel of the graphics it produces, which ultimately enhances the quality of data interpretation and communication.",
|
||||
"links": []
|
||||
},
|
||||
"-cJb8gEBvdVFf7FlgG3Ud": {
|
||||
"title": "Seaborn",
|
||||
@@ -964,19 +890,8 @@
|
||||
},
|
||||
"n3M49lgNPn28hm7kzki-a": {
|
||||
"title": "ggplot2",
|
||||
"description": "ggplot2 is an important and powerful tool in the data analyst's toolkit, especially for visualizing and understanding complex datasets. Built within the R programming language, it provides a flexible, cohesive environment for creating graphs. The main strength of ggplot2 lies in its ability to produce sophisticated and tailored visualizations. This allows data analysts to communicate data-driven findings in an efficient and effective manner, enabling clear communication to stakeholders about relevant insights and patterns identified within the data.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "ggplot2 website",
|
||||
"url": "https://ggplot2.tidyverse.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Make beautiful graphs in R",
|
||||
"url": "https://www.youtube.com/watch?v=qnw1xDnt_Ec",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "ggplot2 is an important and powerful tool in the data analyst's toolkit, especially for visualizing and understanding complex datasets. Built within the R programming language, it provides a flexible, cohesive environment for creating graphs. The main strength of ggplot2 lies in its ability to produce sophisticated and tailored visualizations. This allows data analysts to communicate data-driven findings in an efficient and effective manner, enabling clear communication to stakeholders about relevant insights and patterns identified within the data.",
|
||||
"links": []
|
||||
},
|
||||
"EVk1H-QLtTlpG7lVEenDt": {
|
||||
"title": "Bar Charts",
|
||||
@@ -1140,11 +1055,6 @@
|
||||
"title": "Correlation",
|
||||
"url": "https://www.mathsisfun.com/data/correlation.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What is correlation analysis?",
|
||||
"url": "https://blog.flexmr.net/correlation-analysis-definition-exploration",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1323,8 +1233,8 @@
|
||||
"description": "K-Nearest Neighbors (KNN) is a simple yet powerful algorithm used in the field of machine learning, which a Data Analyst might employ for tasks such as classification or regression. It works based on the principle of proximity, where the prediction of new instance's category depends upon the category of its nearest neighbors. For a Data Analyst working with complex data sets, it's crucial to understand how the KNN algorithm operates, its applicability, pros, and cons. This will facilitate making well-informed decisions about when to utilize it for the best possible outcome in data analysis.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is the k-nearest neighbors (KNN) algorithm?",
|
||||
"url": "https://www.ibm.com/topics/knn#:~:text=The%20k%2Dnearest%20neighbors%20KNN,used%20in%20machine%20learning%20today.",
|
||||
"title": "https://www.ibm.com/topics/knn#:~:text=The k-nearest neighbors (KNN,used in machine learning today.)",
|
||||
"url": "https://www.ibm.com/topics/knn#:~:text=The%20k%2Dnearest%20neighbors%20(KNN,used%20in%20machine%20learning%20today.)",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1355,12 +1265,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"
|
||||
}
|
||||
@@ -1549,18 +1459,7 @@
|
||||
},
|
||||
"iTmtpXe7dR4XKslgpsk2q": {
|
||||
"title": "Data Storage Solutions",
|
||||
"description": "As a business enterprise expands, so does its data. For data analysts, the surge in information means they need efficient and scalable data storage solutions to manage vast volumes of structured and unstructured data, collectively referred to as Big Data. Big Data storage solutions are critical in preserving the integrity of data while also providing quick and easy access to the data when needed. These solutions use software and hardware components to securely store massive amounts of information across numerous servers, allowing data analysts to perform robust data extraction, data processing and complex data analyses. There are several options, from the traditional Relational Database Management Systems (RDBMS) to the more recent NoSQL databases, Hadoop ecosystems, and Cloud storage solutions, each offering unique capabilities and benefits to cater for different big data needs.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "SQL Roadmap",
|
||||
"url": "https://roadmap.sh/sql",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "PostgreSQL Roadmap",
|
||||
"url": "https://roadmap.sh/postgresql-dba",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "As a business enterprise expands, so does its data. For data analysts, the surge in information means they need efficient and scalable data storage solutions to manage vast volumes of structured and unstructured data, collectively referred to as Big Data. Big Data storage solutions are critical in preserving the integrity of data while also providing quick and easy access to the data when needed. These solutions use software and hardware components to securely store massive amounts of information across numerous servers, allowing data analysts to perform robust data extraction, data processing and complex data analyses. There are several options, from the traditional Relational Database Management Systems (RDBMS) to the more recent NoSQL databases, Hadoop ecosystems, and Cloud storage solutions, each offering unique capabilities and benefits to cater for different big data needs.",
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
"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",
|
||||
"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",
|
||||
@@ -2911,39 +3111,8 @@
|
||||
},
|
||||
"h26uS3muFCabe6ekElZcI": {
|
||||
"title": "SWC",
|
||||
"description": "**SWC** (Speedy Web Compiler) is a JavaScript and TypeScript compiler and bundler built in Rust. Unlike Babel, which is JavaScript-based, SWC leverages Rust for blazing-fast performance, making it an ideal choice for large-scale projects. It focuses on speed while offering modern features like tree shaking, JSX transformation, and module bundling, catering to frontend development and build optimization.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "SWC Website",
|
||||
"url": "https://swc.rs/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "SWC Documentation",
|
||||
"url": "https://swc.rs/docs/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "SWC vs Babel: A Rust-Powered Speed Revolution",
|
||||
"url": "https://blog.logrocket.com/swc-vs-babel/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Why SWC is the Future of JavaScript Tooling",
|
||||
"url": "https://dev.to/somelink/why-swc-is-the-future-of-javascript-tooling",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Explore top posts about SWC",
|
||||
"url": "https://app.daily.dev/tags/swc?ref=roadmapsh",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Introduction to SWC",
|
||||
"url": "https://www.youtube.com/watch?v=wlmbNWC3yB8",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"wA2fSYsbBYU02VJXAvUz8": {
|
||||
"title": "Astro",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -405,14 +400,8 @@
|
||||
},
|
||||
"lIb5MeDoqVj6HycveOgTS": {
|
||||
"title": "Computer Graphics",
|
||||
"description": "Computer Graphics is a subfield of computer science that studies methods for digitally synthesizing and manipulating visual content. It involves creating and manipulating visual content using specialized computer software and hardware. This field is primarily used in the creation of digital and video games, CGI in films, and also in visual effects for commercials. The field is divided into two major categories: **Raster graphics** and **Vector graphics**. Raster graphics, also known as bitmap, involve the representation of images through a dot matrix data structure, while Vector graphics involve the use of polygons to represent images in computer graphics. Both of these methods have their unique usage scenarios. Other concepts integral to the study of computer graphics include rendering (including both real-time rendering and offline rendering), animation, and 3D modeling. Generally, computer graphics skills are essential for game developers and animation experts.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "How do Video Game Graphics Work?",
|
||||
"url": "https://www.youtube.com/watch?v=C8YtdC8mxTU",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "Computer Graphics is a subfield of computer science that studies methods for digitally synthesizing and manipulating visual content. It involves creating and manipulating visual content using specialized computer software and hardware. This field is primarily used in the creation of digital and video games, CGI in films, and also in visual effects for commercials. The field is divided into two major categories: **Raster graphics** and **Vector graphics**. Raster graphics, also known as bitmap, involve the representation of images through a dot matrix data structure, while Vector graphics involve the use of polygons to represent images in computer graphics. Both of these methods have their unique usage scenarios. Other concepts integral to the study of computer graphics include rendering (including both real-time rendering and offline rendering), animation, and 3D modeling. Generally, computer graphics skills are essential for game developers and animation experts.",
|
||||
"links": []
|
||||
},
|
||||
"JW5c_0JEtO-OiBoXUia6A": {
|
||||
"title": "Ray Tracing",
|
||||
@@ -441,14 +430,8 @@
|
||||
},
|
||||
"WVgozaQPFbYthZLWMbNUg": {
|
||||
"title": "Rendering Equation",
|
||||
"description": "The **Render Equation**, also known as the **Rendering Equation**, is a fundamental principle in computer graphics that serves as the basis for most advanced lighting algorithms today. First introduced by James Kajiya in 1986, it defines how light interacts with physical objects in a given environment. The equation tries to simulate light's behavior, taking into account aspects such as transmission, absorption, scattering, and emission. The equation can be computationally intensive to solve accurately. It's worth mentioning, however, that many methods have been developed to approximate and solve it, allowing the production of highly realistic images in computer graphics.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Interactive Graphics 12 - The Rendering Equation",
|
||||
"url": "https://www.youtube.com/watch?v=wawf7Am6xy0",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "The **Render Equation**, also known as the **Rendering Equation**, is a fundamental principle in computer graphics that serves as the basis for most advanced lighting algorithms today. First introduced by James Kajiya in 1986, it defines how light interacts with physical objects in a given environment. The equation tries to simulate light's behavior, taking into account aspects such as transmission, absorption, scattering, and emission. The equation can be computationally intensive to solve accurately. It's worth mentioning, however, that many methods have been developed to approximate and solve it, allowing the production of highly realistic images in computer graphics.",
|
||||
"links": []
|
||||
},
|
||||
"eI2jym4AAz3ani-lreSKE": {
|
||||
"title": "Reflection",
|
||||
@@ -462,7 +445,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 +455,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",
|
||||
@@ -519,14 +480,8 @@
|
||||
},
|
||||
"WK6fLWJq9Vh2ySVrSqd-U": {
|
||||
"title": "Color",
|
||||
"description": "In the realm of computer graphics, color plays an integral role. It can be defined in various color models such as RGB (Red, Green, Blue), CYMK (Cyan, Yellow, Magenta, Black), and others. RGB is a color model that combines the primary colors (red, green, blue) in different amounts to produce a spectrum of colors. This model is often used in digital displays. In contrast, CMYK is a color model used in color printing. It uses cyan, magyenta, yellow, and black as the primary colors. HSL (Hue, Saturation, Lightness) and HSV (Hue, Saturation, Value) are other useful models that represent colors based on human perceptions. Another important element of color in computer graphics is the color depth, also known as bit depth, which determines the number of colors that can be displayed at once.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Pixar in a Box - Color",
|
||||
"url": "https://www.khanacademy.org/computing/pixar/animate/ball/v/intro-animation",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "In the realm of computer graphics, color plays an integral role. It can be defined in various color models such as RGB (Red, Green, Blue), CYMK (Cyan, Yellow, Magenta, Black), and others. RGB is a color model that combines the primary colors (red, green, blue) in different amounts to produce a spectrum of colors. This model is often used in digital displays. In contrast, CMYK is a color model used in color printing. It uses cyan, magyenta, yellow, and black as the primary colors. HSL (Hue, Saturation, Lightness) and HSV (Hue, Saturation, Value) are other useful models that represent colors based on human perceptions. Another important element of color in computer graphics is the color depth, also known as bit depth, which determines the number of colors that can be displayed at once.",
|
||||
"links": []
|
||||
},
|
||||
"1S1qPogijW2SQCiF7KLZe": {
|
||||
"title": "Visual Perception",
|
||||
@@ -733,14 +688,8 @@
|
||||
},
|
||||
"ztoW8fBY73Es624A_tjd7": {
|
||||
"title": "Behavior Tree",
|
||||
"description": "The **Behavior Tree** is a decision-making system used in game development, primarily for AI character behavior. These trees help define the actions an AI character will take, based on predefined tasks and conditions. The tree structure starts from a single root, branching out to nodes that represent these decisions or tasks. The tasks can be simple, such as moving from one point to another, or can be complex decisions like whether to attack or retreat. This kind of structure is advantageous because it is easy to add, remove, or modify tasks without breaking the tree or affecting other tasks. This makes it highly flexible and easy to manage, irrespective of the complexity of the tasks.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Unreal Engine 5 Tutorial - AI Part 2: Behavior Tree",
|
||||
"url": "https://www.youtube.com/watch?v=hbHqv9ov8IM&list=PL4G2bSPE_8uklDwraUCMKHRk2ZiW29R6e&index=3&t=16s",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "The **Behavior Tree** is a decision-making system used in game development, primarily for AI character behavior. These trees help define the actions an AI character will take, based on predefined tasks and conditions. The tree structure starts from a single root, branching out to nodes that represent these decisions or tasks. The tasks can be simple, such as moving from one point to another, or can be complex decisions like whether to attack or retreat. This kind of structure is advantageous because it is easy to add, remove, or modify tasks without breaking the tree or affecting other tasks. This makes it highly flexible and easy to manage, irrespective of the complexity of the tasks.",
|
||||
"links": []
|
||||
},
|
||||
"4ZCVUpYrCT14d_JULulLe": {
|
||||
"title": "Fuzzy Logic",
|
||||
@@ -799,25 +748,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",
|
||||
@@ -872,14 +809,8 @@
|
||||
},
|
||||
"PuhXaRZ-Ql5PCqzMyz3en": {
|
||||
"title": "Translucency & Transparency",
|
||||
"description": "In the realm of physically-based rendering, **translucency** and **transparency** act as key aspects in creating visually authentic and compelling images. Transparency refers to the property of an object that allows light to pass through it unhindered, hence making the object clear or invisible. This is commonly seen in materials such as glass, clear plastic, and water. On the other hand, translucency describes how light interacts with a semi-transparent object. Instead of passing directly through, light enters the object, travels within for some distance and then exits at a different location. Common examples of such surfaces include human skin, marble, milk, or wax, which exhibit a soft, diffused lighting effect when light rays pass through them. The technique to achieve this effect in graphics involves subsurface scattering, where incoming light is scattered beneath the object's surface, illuminated it in a way that showcases the material's internal structure.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Learn OpenGL - Blending",
|
||||
"url": "https://learnopengl.com/Advanced-OpenGL/Blending",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "In the realm of physically-based rendering, **translucency** and **transparency** act as key aspects in creating visually authentic and compelling images. Transparency refers to the property of an object that allows light to pass through it unhindered, hence making the object clear or invisible. This is commonly seen in materials such as glass, clear plastic, and water. On the other hand, translucency describes how light interacts with a semi-transparent object. Instead of passing directly through, light enters the object, travels within for some distance and then exits at a different location. Common examples of such surfaces include human skin, marble, milk, or wax, which exhibit a soft, diffused lighting effect when light rays pass through them. The technique to achieve this effect in graphics involves subsurface scattering, where incoming light is scattered beneath the object's surface, illuminated it in a way that showcases the material's internal structure.",
|
||||
"links": []
|
||||
},
|
||||
"H3hkafXO9zqEnWuwHa38P": {
|
||||
"title": "Conservation of Energy",
|
||||
@@ -893,13 +824,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": []
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
@@ -105,7 +100,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "About repositories",
|
||||
"url": "https://docs.github.com/en/repositories/creating-and-managing-repositories/about-repositories",
|
||||
"url": "hhttps://docs.github.com/en/repositories/creating-and-managing-repositories/about-repositories",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -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",
|
||||
@@ -1287,7 +1266,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "--soft documentation",
|
||||
"url": "https://git-scm.com/docs/git-reset#Documentation/git-reset.txt---soft",
|
||||
"url": "https://git-scm.com/docs/git-reset#Documentation/git-reset.txt---hard",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
@@ -1309,7 +1288,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "--mixed documentation",
|
||||
"url": "https://git-scm.com/docs/git-reset#Documentation/git-reset.txt---mixed",
|
||||
"url": "https://git-scm.com/docs/git-reset#Documentation/git-reset.txt---hard",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
@@ -1437,24 +1416,8 @@
|
||||
},
|
||||
"BKVA6Q7DXemAYjyQOA0nh": {
|
||||
"title": "git filter-branch",
|
||||
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.\n\n* Filter types: You can modify trees (e.g., removing a file or running a Perl script) or information about each commit.\n* Preserving original data: The command preserves all original commit times, merge information, and other details unless specified otherwise.\n* Rewriting specific branches: Only the positive refs mentioned in the command line are rewritten; if no filters are specified, commits are recommitted without changes.\n\nNotably, there exists a simpler, safer, and more powerful alternative: `git filter-repo`. This tool is actively promoted by Git and offers a streamlined approach to filtering revisions, making it a preferred choice for rewriting your Git history, especially when managing large repositories.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "git filter-branch",
|
||||
"url": "https://git-scm.com/docs/git-filter-branch",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "git filter-repo",
|
||||
"url": "https://github.com/newren/git-filter-repo",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Removing sensitive data from a repository",
|
||||
"url": "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.",
|
||||
"links": []
|
||||
},
|
||||
"OQOmxg9mCfcjt80hpvXkA": {
|
||||
"title": "git push --force",
|
||||
@@ -2219,7 +2182,7 @@
|
||||
},
|
||||
"qrdOARfqGPF9xhF6snbAn": {
|
||||
"title": "OAuth Apps",
|
||||
"description": "GitHub OAuth Apps allow developers to integrate with GitHub using OAuth 2.0 authentication. They enable secure, token-based access to specific GitHub resources like repositories, issues, and pull requests. OAuth Apps can automate tasks, personalize interactions, and provide real-time notifications through webhooks, all while allowing users to approve only the necessary permissions without sharing their credentials.\n\nVisit the following resources to learn more:",
|
||||
"description": "GitHub OAuth Apps are a way to integrate with the GitHub platform using OAuth authentication. They allow developers to create custom integrations that can automate tasks, provide real-time notifications, and build custom workflows.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Creating an OAuth app",
|
||||
|
||||
@@ -965,11 +965,6 @@
|
||||
"title": "SwiftUI",
|
||||
"description": "SwiftUI is Apple's modern declarative framework for building user interfaces across all Apple platforms. Introduced in 2019, it allows developers to create UIs using Swift code, describing the desired layout and behavior rather than implementing them imperatively. SwiftUI offers a more concise and intuitive approach to UI development, with features like automatic support for Dark Mode, dynamic type, and localization. It uses a state-driven approach, automatically updating the UI when underlying data changes. While newer than UIKit, SwiftUI is rapidly evolving and gaining adoption, offering seamless integration with UIKit when needed.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "HackingWithSwift - 100 Days of SwiftUI",
|
||||
"url": "https://www.hackingwithswift.com/100/swiftui",
|
||||
"type": "course"
|
||||
},
|
||||
{
|
||||
"title": "SwiftUI Documentation",
|
||||
"url": "https://developer.apple.com/xcode/swiftui/",
|
||||
|
||||
@@ -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\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "JavaScript Scope",
|
||||
@@ -423,17 +423,12 @@
|
||||
},
|
||||
"1RcwBHU3jzx0YxxUGZic4": {
|
||||
"title": "string",
|
||||
"description": "String is a primitive type that holds a sequence of characters. String in Javascript is written within a pair of single quotation marks `''`, double quotation marks `\"\"`, or backticks ` `` ` (template literals). All types of quotes can be used to contain a string but only if the starting quote is the same as the end quote.\n\nVisit the following resources to learn more:",
|
||||
"description": "String is a primitive type that holds a sequence of characters. String in Javascript is written within a pair of single quotation marks `''` or double quotation marks `\"\"`. Both quotes can be used to contain a string but only if the starting quote is the same as the end quote.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"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",
|
||||
@@ -1814,46 +1779,18 @@
|
||||
},
|
||||
"gsyY3Oa3Jf0W5K_lyqBYO": {
|
||||
"title": "call",
|
||||
"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",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"-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 +1935,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 +1998,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 +2014,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 +2113,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 +2197,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 +2229,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 +2264,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 +2287,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",
|
||||
|
||||
@@ -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. It’s 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",
|
||||
@@ -393,7 +388,7 @@
|
||||
},
|
||||
"o6GQ3-8DgDtHzdX6yeg1w": {
|
||||
"title": "Flink",
|
||||
"description": "Apache Flink is an open-source stream processing framework designed for real-time and batch data processing with low latency and high throughput. It supports event time processing, fault tolerance, and stateful operations, making it ideal for applications like real-time analytics, fraud detection, and event-driven systems. Flink is highly scalable, integrates with various data systems, and is widely used in industries for large-scale, real-time data processing tasks.\n\nVisit the following resources to learn more:",
|
||||
"description": "Apache Flink is a distributed stream processing framework that is used to process large amounts of data in real-time. It is designed to be highly scalable and fault-tolerant. Flink is built on top of the Apache Kafka messaging system and is used to process data streams in real-time.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Apache Flink Documentation",
|
||||
@@ -404,11 +399,6 @@
|
||||
"title": "Explore top posts about Apache Flink",
|
||||
"url": "https://app.daily.dev/tags/apache-flink?ref=roadmapsh",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Apache Flink Tutorialpoint",
|
||||
"url": "https://www.tutorialspoint.com/apache_flink/apache_flink_introduction.htm",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
@@ -1942,11 +1942,6 @@
|
||||
"title": "Child Process Docs",
|
||||
"url": "https://nodejs.org/api/child_process.html#child-process",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Securing Node.js Against Command Injection",
|
||||
"url": "https://www.nodejs-security.com/blog/securing-your-nodejs-apps-by-analyzing-real-world-command-injection-examples",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2085,58 +2080,7 @@
|
||||
},
|
||||
"M62lAWBOrTe99TfpFOQ-Y": {
|
||||
"title": "Common Built-in Modules",
|
||||
"description": "These are the core modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Nodejs fs module",
|
||||
"url": "https://nodejs.org/api/fs.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs url module",
|
||||
"url": "https://nodejs.org/api/url.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs console module",
|
||||
"url": "https://nodejs.org/api/console.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs util module",
|
||||
"url": "https://nodejs.org/api/util.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs events module",
|
||||
"url": "https://nodejs.org/api/events.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs os module",
|
||||
"url": "https://nodejs.org/api/os.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs worker threads module",
|
||||
"url": "https://nodejs.org/api/worker_threads.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs child process module",
|
||||
"url": "https://nodejs.org/api/child_process.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs process object",
|
||||
"url": "https://nodejs.org/api/process.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs crypto module",
|
||||
"url": "https://nodejs.org/api/crypto.html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "These are the common modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.",
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -220,18 +220,7 @@
|
||||
"0uiGsC5SWavNdlFqizkKe": {
|
||||
"title": "Rendering",
|
||||
"description": "React follows a declarative approach to rendering components, which means that developers specify what a component should look like, and React takes care of rendering the component to the screen. This is in contrast to an imperative approach, where developers would write code to manually manipulate the DOM (Document Object Model) to update the UI.\n\nThe virtual DOM (VDOM) is an important aspect of how React works. It is a lightweight in-memory representation of the DOM (Document Object Model), and it is used to optimize the rendering of components in a React application.\n\n* Components are written as JavaScript classes or functions that define a render method. The render method returns a description of what the component should look like, using JSX syntax.\n* When a component is rendered, React creates a virtual DOM (VDOM) representation of the component. The VDOM is a lightweight in-memory representation of the DOM, and it is used to optimize the rendering of components.\n* React compares the VDOM representation of the component with the previous VDOM representation (if it exists). If there are differences between the two VDOMs, React calculates the minimum number of DOM updates needed to bring the actual DOM into line with the new VDOM.\n* React updates the actual DOM with the minimum number of DOM updates needed to reflect the changes in the VDOM.\n\nThis process is known as reconciliation, and it is an important aspect of how React works. By using a declarative approach and a VDOM, React is able to optimize the rendering of components and improve the performance of web applications.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Rendering - Official Docs",
|
||||
"url": "https://legacy.reactjs.org/docs/rendering-elements.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Rendering in React - ui.dev",
|
||||
"url": "https://ui.dev/why-react-renders",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"8OBlgDRUg-CTgDXY-QHyO": {
|
||||
"title": "Component Lifecycle",
|
||||
@@ -565,7 +554,7 @@
|
||||
},
|
||||
"t_laNdMmdLApYszqXRdWg": {
|
||||
"title": "useRef",
|
||||
"description": "`useRef` is a React hook that provides a way to create a mutable reference that persists across component re-renders. It stores a value that doesn't cause re-renders when it changes.\n\nVisit the following resources to learn more:",
|
||||
"description": "useRef is a React hook that provides a way to create a mutable reference that persists across component re-renders. It stores a value that doesn't cause re-renders when it changes.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "useRef",
|
||||
@@ -586,7 +575,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",
|
||||
@@ -602,7 +591,7 @@
|
||||
},
|
||||
"v48Mv0wQqjXbvy8x6gDjQ": {
|
||||
"title": "useReducer",
|
||||
"description": "`useReducer`: An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)\n\nLearn more from the following resources:",
|
||||
"description": "useReducer: An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method. (If you’re familiar with Redux, you already know how this works.)\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "useReducer Docs",
|
||||
@@ -686,23 +675,8 @@
|
||||
},
|
||||
"mkyU0ug8MXxV4biHuOity": {
|
||||
"title": "Hooks Best Practices",
|
||||
"description": "To fully leverage the capabilities of React Hooks, it is crucial to adopt best practices that not only enhance code readability but also optimize performance. By adhering to these practices, developers can create cleaner, more maintainable components that make the most of React's powerful features, leading to a more efficient and enjoyable development experience.\n\nLearn more from the following resources:",
|
||||
"description": "Learn the best practices for using React hooks from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Rules of Hooks",
|
||||
"url": "https://react.dev/reference/rules/rules-of-hooks/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "React Hooks Best Practices: Unlocking Efficiency and Elegance",
|
||||
"url": "https://medium.com/womenintechnology/react-hooks-best-practices-unlocking-efficiency-and-elegance-da23f7e1418a",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Mastering React Hooks: Best Practices and Common Pitfalls",
|
||||
"url": "https://dev.to/codesensei/mastering-react-hooks-best-practices-and-common-pitfalls-3d9i",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "React Hooks Cheat Sheet: Best Practices with Examples",
|
||||
"url": "https://blog.logrocket.com/react-hooks-cheat-sheet-solutions-common-problems/",
|
||||
@@ -720,12 +694,12 @@
|
||||
"description": "Routing is an essential concept in Single Page Applications (SPA). When your application is divided into separated logical sections, and all of them are under their own URL, your users can easily share links among each other.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "How to use Routing in React JS: A Comprehensive Guide.",
|
||||
"title": "How to use Routing in React JS: A Comprehensive Guide. ",
|
||||
"url": "https://blog.logrocket.com/react-router-v6-guide/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "React Router 6 – Tutorial for Beginners.",
|
||||
"title": "React Router 6 – Tutorial for Beginners. ",
|
||||
"url": "https://www.youtube.com/watch?v=59IXY5IDrBA",
|
||||
"type": "video"
|
||||
}
|
||||
@@ -962,19 +936,8 @@
|
||||
},
|
||||
"thfnymb_UIiKxakKfiua5": {
|
||||
"title": "Component / Libraries",
|
||||
"description": "React component libraries are collections of pre-built, reusable components that can be used to speed up the development process. They can be styled using CSS in various ways, including traditional CSS files, CSS modules, and CSS-in-JS solutions like styled-components.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "MUI: React Component Library",
|
||||
"url": "https://mui.com/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "NextUI.org",
|
||||
"url": "https://nextui.org/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "React component libraries are collections of pre-built, reusable components that can be used to speed up the development process. They can be styled using CSS in various ways, including traditional CSS files, CSS modules, and CSS-in-JS solutions like styled-components.",
|
||||
"links": []
|
||||
},
|
||||
"akVNUPOqaTXaSHoQFlkP_": {
|
||||
"title": "Panda CSS",
|
||||
@@ -1618,33 +1581,12 @@
|
||||
},
|
||||
"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:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Official Website",
|
||||
"url": "https://www.typescriptlang.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "The TypeScript Handbook",
|
||||
"url": "https://www.typescriptlang.org/docs/handbook/intro.html",
|
||||
"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",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"K3RZ8ESxWCpLKHePF87Hy": {
|
||||
"title": "Zod",
|
||||
"description": "Zod is a TypeScript-first schema declaration and validation library. I'm using the term \"schema\" to broadly refer to any data type, from a simple string to a complex nested object.\n\nZod is designed to be as developer-friendly as possible. The goal is to eliminate duplicate type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures.\n\nVisit the following resources to learn more:",
|
||||
"description": "Zod is a TypeScript-first schema declaration and validation library. I'm using the term \"schema\" to broadly refer to any data type, from a simple string to a complex nested object.\n\nZod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Zod Website",
|
||||
@@ -1729,24 +1671,8 @@
|
||||
},
|
||||
"y2dI1DVLWKAkv6VRpgaQa": {
|
||||
"title": "GSock",
|
||||
"description": "`GSAP` (GreenSock Animation Platform) is a framework-agnostic JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in every major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths, generic objects...anything JavaScript can touch!\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "GSAP: Homepage",
|
||||
"url": "https://gsap.com/docs/v3/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "The Beginner's Guide to the GreenSock Animation Platform",
|
||||
"url": "https://www.freecodecamp.org/news/the-beginners-guide-to-the-greensock-animation-platform-7dc9fd9eb826/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Intro to Greensock Web Animation",
|
||||
"url": "https://www.youtube.com/watch?v=EOa7ccPWvXg",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"_F3WMxhzaK9F8_-zHDDMF": {
|
||||
"title": "Suspense",
|
||||
|
||||
@@ -315,6 +315,11 @@
|
||||
"url": "https://www.tutorialspoint.com/typescript/index.htm",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Scrimba — TypeScript Basics",
|
||||
"url": "https://scrimba.com/learn/typescript",
|
||||
"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
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 isn’t 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 we’ve 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 we’d 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",
|
||||
|
||||
@@ -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",
|
||||
@@ -64,7 +69,7 @@
|
||||
},
|
||||
"kcG4IpneJzA6di0uqTiwb": {
|
||||
"title": "CREATE Action Funnel",
|
||||
"description": "Stephen Wendel's CREATE Action Funnel is a behavioral design framework aimed at helping individuals or organizations encourage specific behaviors in others, especially in the context of product design. It breaks down the process of motivating action into six key stages. Each stage helps identify where users might drop off or face barriers, allowing designers or strategists to address these pain points effectively. These stages are:\n\n* **CUE:** The user must notice a cue or prompt that tells them to act. This could be a notification, a visual element, or any kind of reminder.\n \n* **REACTION:** The user must react positively to the cue. This stage involves emotional and cognitive processing, where the individual decides if the action is relevant or attractive.\n \n* **EVALUATION:** The user evaluates whether the action is worth their time, energy, or resources. They assess the benefits versus the effort required.\n \n* **ABILITY:** The user must feel capable of taking the action. This involves ensuring that the action is easy enough to do and aligns with their skills and resources.\n \n* **TIMING:** The action needs to happen at the right time. Users need to have the opportunity and be in the right context to act.\n \n* **EXECUTION:** Finally, the action must be carried out successfully. This is the stage where the behavior is completed.\n \n\nThe CREATE Action Funnel is helpful for product designers, marketers, or behavior change professionals, as it provides a structured way to understand user actions and design interventions to improve completion rates. It identifies and solves the gaps that occur between intention and action.",
|
||||
"description": "Stephen Wendell's Create Action Funnel is a UX design framework focused on converting website visitors into active customers through a systematic and engaging process. The approach emphasizes on understanding user behavior, catering to their needs, and directing them towards specific actions. The Action Funnel consists of four major steps:\n\n* **Establish the Objectives:** Before diving into the design, clearly define the goals you want to achieve through the website or app. Determine what actions you want the users to take (e.g., sign up, make a purchase, share content) and what constitutes a successful conversion.\n \n* **Understand User Mindsets:** Identify your target audience and recognize their needs, preferences, emotions, and pain points. Accomplishing this requires user research, creating personas, storyboarding, and empathy mapping, among other methods.\n \n* **Design the Optimal User Flow:** Craft a seamless and intuitive user journey by designing a clear path from the landing page to the desired action. Prioritize simplicity, usability, and efficiency. Make sure to include meaningful touchpoints and interactions to engage the users and make it easy for them to complete the intended action.\n \n* **Refine and Test the Experience:** Use wireframes and prototypes to test and iteratively refine the user experience. Employ user testing, A/B testing, and analytics to gather insight on user behavior, preferences, and engagement. Continuously use feedback to make improvements, ensuring that the design effectively leads visitors down the action funnel.\n \n\nBy implementing Stephen Wendell's `Create Action Funnel`, you can effectively guide users through an engaging journey that motivates them to become active customers, ultimately increasing conversion rates and overall satisfaction.",
|
||||
"links": [
|
||||
{
|
||||
"title": "Behavioral Science Crash Course: Steve Wendel's CREATE Action Funnel",
|
||||
@@ -75,7 +80,7 @@
|
||||
},
|
||||
"0Df110GZcDw5wbAe1eKoA": {
|
||||
"title": "Spectrum of Thinking Interventions",
|
||||
"description": "The _Spectrum of Thinking Interventions_ provides a structure for understanding the different types of decision-making processes by illustrating how our minds would respond in a _default, lowest energy way_, if we didn't consciously do something different. This spectrum ranges from situations requiring minimal thought to those demanding intensive thinking, and includes the mechanisms (\"interventions\") that our minds will likely use.\n\n* **Habits:** Triggering a learned routine based on familiar cues\n* **Other intuitive responses:** Used in familiar or semi-familiar situations, with responses based on past experiences\n* **Active mindset or self-concept:** Used in ambiguous scenarios with multiple possible interpretations\n* **Heuristics:** Used in situations requiring conscious attention, but where decisions can be made more easily\n* **Focused, conscious calculation:** Used in unfamiliar scenarios or crucial decisions where deliberate focus is needed\n\nWith this spectrum in mind, it is essential as a UX designer to leverage on the mind's decision-making process, analyze which mechanisms are most applicable to your target users and design the most accessible and effective solutions.",
|
||||
"description": "The _Spectrum of Thinking Interventions_ provides a structure to guide your UX design process, helping you identify the types and range of thinking interventions that the user may require. This spectrum encompasses four primary categories: guidance, explanation, exploration, and creation.\n\nGuidance\n--------\n\nGuidance-based interventions are designed to help users navigate through a digital product or service with minimal effort. They may be aimed at full-fledged beginners, casual users, or experts in their respective domains. Such interventions may include signposts, tooltips, and clearly articulated labels.\n\n_Examples:_\n\n* Visual cues (e.g., icons, colors)\n* Signposting (e.g., breadcrumbs)\n* In-context information (e.g., tool tips, hints)\n\nExplanation\n-----------\n\nExplanation-based interventions provide users with detailed narratives, overviews, or background information that helps them make informed decisions. This may include tutorials, articles, videos, or any other mediums that help explain complex concepts or instructions.\n\n_Examples:_\n\n* Multimedia tutorials\n* Articles or blog posts\n* Infographics or diagrams\n\nExploration\n-----------\n\nExploration-based interventions encourage users to understand and interact with the product by investigating, asking questions, or searching for solutions on their own. This can be done by providing interactive elements, multiple pathways, and opportunities for discovery.\n\n_Examples:_\n\n* Interactive simulations or models\n* Advanced search capabilities\n* Multiple UI paths for task completion\n\nCreation\n--------\n\nCreation-based interventions engage users by offering them the tools and resources to co-create or customize their experience. This type of intervention often involves a more extensive level of input and involvement from the user as they become active participants in the design process.\n\n_Examples:_\n\n* Customizable user interfaces\n* Allowing users to create their content\n* Enabling users to manage their preferences, settings, and configurations\n\nWith this spectrum in mind, it is essential as a UX designer to analyze which types of thinking interventions are most relevant to your target users and design the most accessible and effective solutions. Always consider how these interventions will influence users' decision-making processes and their overall satisfaction with your digital product or service.",
|
||||
"links": []
|
||||
},
|
||||
"kWA8CvocP1pkom2N7O4gb": {
|
||||
|
||||
@@ -242,25 +242,13 @@
|
||||
},
|
||||
"NCIzs3jbQTv1xXhAaGfZN": {
|
||||
"title": "v-text",
|
||||
"description": "The `v-text` directive is used to set the textContent property of an element. It's important to note that when using this directive it will overwrite the HTML content inside the element. The expected input is a string, so it's important to wrap any text in single quotes.\n\nExample:\n\n <template>\n <p v-text=\"'I am some text'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-text documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-text",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"bZxtIBeIfeUcR32LZWrPW": {
|
||||
"title": "v-html",
|
||||
"description": "The `v-thml` directive is similar to the `v-text` directive, but the difference is that `v-html` renders its content as HTML. This means that if you pass an HTML element it will be rendered as an element and not plain text. Since the content is render as HTMl, it can pose a security risk if the content contains malicius JavaScript code. For this reason you should never use this directive in combination with user input, unless the input is first properly sanitized.\n\nExample:\n\n <template>\n <p v-html=\"'<h1>Text</h1>'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-html documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"_TlbGTKFCMO0wdLbC6xHX": {
|
||||
"title": "v-show",
|
||||
@@ -297,25 +285,13 @@
|
||||
},
|
||||
"a9caVhderJaVo0v14w8WB": {
|
||||
"title": "v-else-if",
|
||||
"description": "This directive is used to add additional conditions to a v-if and v-else block.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-else-if Documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-else-if",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"3ftwRjQ9e1-qDT9BV53zr": {
|
||||
"title": "v-for",
|
||||
"description": "The `v-for` directive is used to render an HTML element, a block of elements, or even a component based on an array, an object, or a set number of times. When using this directive it is important to assign a unique key to each item to avoid issues and improve perfomance. This directive follows the `item in items` syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const foods = ref([\n {id: 1, name: \"apple\"},\n {id: 2, name: \"pear\"},\n {id: 3, name: \"pizza\"}\n ]);\n </script>\n \n <template>\n <p v-for=\"food in foods\" :key=\"food.id\">{{ food.name }}</p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-for documentation",
|
||||
"url": "https://vuejs.org/guide/essentials/list#v-for",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"hVuRmhXVP65IPtuHTORjJ": {
|
||||
"title": "v-on",
|
||||
@@ -324,14 +300,8 @@
|
||||
},
|
||||
"cuM9q9vYy8JpZPGeBffd1": {
|
||||
"title": "v-bind",
|
||||
"description": "The `v-bind` directive dynamically binds an HTML attribute to data.\n\nThe shorthand for this directive is `:`\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const image_url = ref(\"path/to/image.png\")\n </script>\n \n <template>\n <img :src=\"image_url\" />\n </template>\n \n\nVisit the following resources for more information:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-bind documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-bind",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"cxu2Wbt306SxM4JKQQqnL": {
|
||||
"title": "v-model",
|
||||
@@ -351,25 +321,13 @@
|
||||
},
|
||||
"5k9CrbzhNy9iiS6ez2UE6": {
|
||||
"title": "v-once",
|
||||
"description": "The `v-once` directive makes an HTML element render only once, skipping every future update.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const input = ref(\"Some Text\");\n </script>\n \n <template>\n <input v-model=\"input\">\n <p v-once>{{ input }}</p>\n </template>\n \n\nIn this example the **p** element will not change its text even if the input variable is changed through the **input** element.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-once documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-once",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"mlsrhioiEkqnRIL6O3hNa": {
|
||||
"title": "v-pre",
|
||||
"description": "The `v-pre` directive makes an element render its content as-is, skipping its compilation. The most common use case is when displaying raw mustache syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const text = ref(\"Some Text\")\n </script>\n \n <template>\n <p v-pre >{{ text }}</p>\n </template>\n \n\nThe **p** element will display: `{{ text }}` and not `Some Text` because the compilation is skipped.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-pre Documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-pre",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"RrSekP8Ub01coegMwLP6a": {
|
||||
"title": "v-cloak",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`,
|
||||
{},
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
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 { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { StreakDay } from './StreakDay';
|
||||
import {
|
||||
navigationDropdownOpen,
|
||||
roadmapsDropdownOpen,
|
||||
} 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;
|
||||
longestCount: number;
|
||||
previousCount?: number | null;
|
||||
firstVisitAt: Date;
|
||||
lastVisitAt: Date;
|
||||
};
|
||||
|
||||
type AccountStreakProps = {};
|
||||
|
||||
export function AccountStreak(props: AccountStreakProps) {
|
||||
const toast = useToast();
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const accountStreak = useStore($accountStreak);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
|
||||
const $roadmapsDropdownOpen = useStore(roadmapsDropdownOpen);
|
||||
const $navigationDropdownOpen = useStore(navigationDropdownOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if ($roadmapsDropdownOpen || $navigationDropdownOpen) {
|
||||
setShowDropdown(false);
|
||||
}
|
||||
}, [$roadmapsDropdownOpen, $navigationDropdownOpen]);
|
||||
|
||||
const loadAccountStreak = async () => {
|
||||
if (!isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountStreak) {
|
||||
setIsLoading(false);
|
||||
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');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$accountStreak.set(response);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useOutsideClick(dropdownRef, () => {
|
||||
setShowDropdown(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadAccountStreak().finally(() => {});
|
||||
}, []);
|
||||
|
||||
if (!isLoggedIn() || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let { count: currentCount = 0 } = accountStreak || {};
|
||||
const previousCount =
|
||||
accountStreak?.previousCount || accountStreak?.count || 0;
|
||||
|
||||
// Adding one to show the current day
|
||||
const currentCircleCount = Math.min(currentCount, 5) + 1;
|
||||
// Adding one day to show the streak they broke
|
||||
const leftCircleCount = Math.min(5 - currentCircleCount, previousCount) + 1;
|
||||
// In the maximum case, we will show 10 circles
|
||||
const remainingCount = Math.max(0, 10 - leftCircleCount - currentCircleCount);
|
||||
const totalCircles = leftCircleCount + currentCircleCount + remainingCount;
|
||||
|
||||
return (
|
||||
<div className="relative z-[90] animate-fade-in">
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center justify-center rounded-lg p-1.5 px-2 text-purple-400 hover:bg-purple-100/10 focus:outline-none',
|
||||
{
|
||||
'bg-purple-100/10': showDropdown,
|
||||
},
|
||||
)}
|
||||
onClick={() => setShowDropdown(true)}
|
||||
>
|
||||
<Zap strokeWidth={1} className="size-5 fill-current" />
|
||||
<span className="ml-1.5 text-sm font-semibold">
|
||||
{accountStreak?.count}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{showDropdown && (
|
||||
<div
|
||||
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="flex items-center justify-between gap-2 text-sm text-slate-500">
|
||||
<p>
|
||||
Current Streak
|
||||
<span className="ml-2 font-medium text-white">
|
||||
{accountStreak?.count || 0}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Longest Streak
|
||||
<span className="ml-2 font-medium text-white">
|
||||
{accountStreak?.longestCount || 0}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-5 mt-8">
|
||||
<div className="grid grid-cols-10 gap-1">
|
||||
{Array.from({ length: totalCircles }).map((_, index) => {
|
||||
let dayCount,
|
||||
icon,
|
||||
isPreviousStreakDay,
|
||||
isBrokenStreakDay,
|
||||
isCurrentStreakDay,
|
||||
isRemainingStreakDay,
|
||||
isToday;
|
||||
|
||||
if (index < leftCircleCount) {
|
||||
// Previous streak days
|
||||
dayCount = previousCount - leftCircleCount + index + 1 + 1;
|
||||
isPreviousStreakDay = true;
|
||||
isBrokenStreakDay = index === leftCircleCount - 1;
|
||||
|
||||
icon = isBrokenStreakDay ? (
|
||||
<ZapOff className="size-5 fill-current" />
|
||||
) : (
|
||||
<Zap className="size-5 fill-current" />
|
||||
);
|
||||
} else if (index < leftCircleCount + currentCircleCount) {
|
||||
// Current streak days
|
||||
const currentIndex = index - leftCircleCount;
|
||||
dayCount =
|
||||
currentCount - currentCircleCount + currentIndex + 1 + 1;
|
||||
isCurrentStreakDay = true;
|
||||
isToday = currentIndex === currentCircleCount - 1;
|
||||
icon = <Zap className="size-5 fill-current" />;
|
||||
} else {
|
||||
// Remaining streak days
|
||||
const remainingIndex =
|
||||
index - leftCircleCount - currentCircleCount;
|
||||
dayCount = currentCount + remainingIndex + 1 + 1;
|
||||
isRemainingStreakDay = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<StreakDay
|
||||
key={`streak-${index}`}
|
||||
dayCount={dayCount}
|
||||
icon={icon}
|
||||
isBrokenStreakDay={isBrokenStreakDay}
|
||||
isPreviousStreakDay={isPreviousStreakDay}
|
||||
isCurrentStreakDay={isCurrentStreakDay}
|
||||
isRemainingStreakDay={isRemainingStreakDay}
|
||||
isToday={isToday}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
.react-calendar-heatmap text {
|
||||
fill: rgb(148, 163, 184) !important;
|
||||
}
|
||||
|
||||
.react-calendar-heatmap rect:hover {
|
||||
stroke: rgb(148, 163, 184) !important;
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import CalendarHeatmap from 'react-calendar-heatmap';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatActivityDate } from '../../lib/date';
|
||||
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||
import 'react-calendar-heatmap/dist/styles.css';
|
||||
import './AccountStreakHeatmap.css';
|
||||
|
||||
const legends = [
|
||||
{ count: 1, color: 'bg-slate-600' },
|
||||
{ count: 3, color: 'bg-slate-500' },
|
||||
{ count: 5, color: 'bg-slate-400' },
|
||||
{ count: 10, color: 'bg-slate-300' },
|
||||
{ count: 20, color: 'bg-slate-200' },
|
||||
];
|
||||
|
||||
type AccountStreakHeatmapProps = {};
|
||||
|
||||
export function AccountStreakHeatmap(props: AccountStreakHeatmapProps) {
|
||||
const startDate = dayjs().subtract(6, 'months').toDate();
|
||||
const endDate = dayjs().toDate();
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<CalendarHeatmap
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
values={[
|
||||
{
|
||||
date: '2024-08-01',
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
date: '2024-08-02',
|
||||
count: 10,
|
||||
},
|
||||
{
|
||||
date: '2024-08-03',
|
||||
count: 5,
|
||||
},
|
||||
{
|
||||
date: '2024-08-04',
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
date: '2024-08-05',
|
||||
count: 7,
|
||||
},
|
||||
{
|
||||
date: '2024-08-06',
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
date: '2024-08-07',
|
||||
count: 6,
|
||||
},
|
||||
{
|
||||
date: '2024-08-08',
|
||||
count: 8,
|
||||
},
|
||||
{
|
||||
date: '2024-08-09',
|
||||
count: 9,
|
||||
},
|
||||
{
|
||||
date: '2024-08-10',
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
date: '2024-08-11',
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
date: '2024-08-12',
|
||||
count: 5,
|
||||
},
|
||||
{
|
||||
date: '2024-08-13',
|
||||
count: 7,
|
||||
},
|
||||
{
|
||||
date: '2024-08-14',
|
||||
count: 8,
|
||||
},
|
||||
{
|
||||
date: '2024-08-15',
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
date: '2024-08-16',
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
date: '2024-08-17',
|
||||
count: 6,
|
||||
},
|
||||
{
|
||||
date: '2024-08-18',
|
||||
count: 8,
|
||||
},
|
||||
{
|
||||
date: '2024-08-19',
|
||||
count: 10,
|
||||
},
|
||||
{
|
||||
date: '2024-08-20',
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
date: '2024-08-21',
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
date: '2024-08-22',
|
||||
count: 6,
|
||||
},
|
||||
{
|
||||
date: '2024-08-23',
|
||||
count: 8,
|
||||
},
|
||||
{
|
||||
date: '2024-08-24',
|
||||
count: 10,
|
||||
},
|
||||
{
|
||||
date: '2024-08-25',
|
||||
count: 30,
|
||||
},
|
||||
]}
|
||||
classForValue={(value) => {
|
||||
if (!value) {
|
||||
return 'fill-slate-700 rounded-md [rx:2px] focus:outline-none';
|
||||
}
|
||||
|
||||
const { count } = value;
|
||||
if (count >= 20) {
|
||||
return 'fill-slate-200 rounded-md [rx:2px] focus:outline-none';
|
||||
} else if (count >= 10) {
|
||||
return 'fill-slate-300 rounded-md [rx:2px] focus:outline-none';
|
||||
} else if (count >= 5) {
|
||||
return 'fill-slate-400 rounded-md [rx:2px] focus:outline-none';
|
||||
} else if (count >= 3) {
|
||||
return 'fill-slate-500 rounded-md [rx:2px] focus:outline-none';
|
||||
} else {
|
||||
return 'fill-slate-600 rounded-md [rx:2px] focus:outline-none';
|
||||
}
|
||||
}}
|
||||
tooltipDataAttrs={(value: any) => {
|
||||
if (!value || !value.date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedDate = formatActivityDate(value.date);
|
||||
return {
|
||||
'data-tooltip-id': 'user-activity-tip',
|
||||
'data-tooltip-content': `${value.count} Updates - ${formattedDate}`,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactTooltip
|
||||
id="user-activity-tip"
|
||||
className="!rounded-lg !bg-slate-900 !p-1 !px-2 !text-xs"
|
||||
/>
|
||||
|
||||
<div className="mt-2 flex items-center justify-end">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-xs text-slate-500">Less</span>
|
||||
{legends.map((legend) => (
|
||||
<div
|
||||
key={legend.count}
|
||||
className="flex items-center"
|
||||
data-tooltip-id="user-activity-tip"
|
||||
data-tooltip-content={`${legend.count} Updates`}
|
||||
>
|
||||
<div
|
||||
className={`h-2.5 w-2.5 ${legend.color} mr-1 rounded-sm`}
|
||||
></div>
|
||||
</div>
|
||||
))}
|
||||
<span className="ml-2 text-xs text-slate-500">More</span>
|
||||
<ReactTooltip
|
||||
id="user-activity-tip"
|
||||
className="!rounded-lg !bg-slate-900 !p-1 !px-2 !text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
type StreakDayProps = {
|
||||
isToday?: boolean;
|
||||
isCurrentStreakDay?: boolean;
|
||||
isPreviousStreakDay?: boolean;
|
||||
isBrokenStreakDay?: boolean;
|
||||
isRemainingStreakDay?: boolean;
|
||||
dayCount: number;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
export function StreakDay(props: StreakDayProps) {
|
||||
const {
|
||||
isCurrentStreakDay,
|
||||
isPreviousStreakDay,
|
||||
isBrokenStreakDay,
|
||||
isRemainingStreakDay,
|
||||
dayCount,
|
||||
icon,
|
||||
isToday = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex flex-col items-center justify-center gap-1.5',
|
||||
{
|
||||
'text-red-400 opacity-40': isPreviousStreakDay,
|
||||
'text-slate-600': isRemainingStreakDay,
|
||||
'text-yellow-300': isCurrentStreakDay,
|
||||
'text-slate-400': isToday,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn('flex size-6 items-center justify-center rounded-full', {
|
||||
'bg-slate-700': isRemainingStreakDay,
|
||||
'border border-dashed border-slate-500 striped-bg': isToday,
|
||||
})}
|
||||
>
|
||||
{isToday ? null : icon}
|
||||
</div>
|
||||
<span className={cn('text-xs')}>{dayCount}</span>
|
||||
{isToday && (
|
||||
<ChevronDown className="absolute bottom-full left-1/2 h-3.5 w-3.5 -translate-y-[0.75px] -translate-x-1/2 transform stroke-[2.5px] text-slate-400" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,10 +5,6 @@ import { ResourceProgress } from './ResourceProgress';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { EmptyActivity } from './EmptyActivity';
|
||||
import { ActivityStream, type UserStreamActivity } from './ActivityStream';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { ProjectProgress } from './ProjectProgress';
|
||||
|
||||
type ProgressResponse = {
|
||||
updatedAt: string;
|
||||
@@ -51,14 +47,11 @@ export type ActivityResponse = {
|
||||
};
|
||||
}[];
|
||||
activities: UserStreamActivity[];
|
||||
projects: ProjectStatusDocument[];
|
||||
};
|
||||
|
||||
export function ActivityPage() {
|
||||
const toast = useToast();
|
||||
const [activity, setActivity] = useState<ActivityResponse>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [projectDetails, setProjectDetails] = useState<PageType[]>([]);
|
||||
|
||||
async function loadActivity() {
|
||||
const { error, response } = await httpGet<ActivityResponse>(
|
||||
@@ -75,29 +68,11 @@ export function ActivityPage() {
|
||||
setActivity(response);
|
||||
}
|
||||
|
||||
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([loadActivity(), loadAllProjectDetails()]).finally(
|
||||
() => {
|
||||
pageProgressMessage.set('');
|
||||
setIsLoading(false);
|
||||
},
|
||||
);
|
||||
loadActivity().finally(() => {
|
||||
pageProgressMessage.set('');
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const learningRoadmaps = activity?.learning.roadmaps || [];
|
||||
@@ -131,17 +106,6 @@ export function ActivityPage() {
|
||||
learningRoadmapsToShow.length !== 0 ||
|
||||
learningBestPracticesToShow.length !== 0;
|
||||
|
||||
const enrichedProjects = activity?.projects.map((project) => {
|
||||
const projectDetail = projectDetails.find(
|
||||
(page) => page.id === project.projectId,
|
||||
);
|
||||
|
||||
return {
|
||||
...project,
|
||||
title: projectDetail?.title || 'N/A',
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActivityCounters
|
||||
@@ -237,19 +201,6 @@ export function ActivityPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{enrichedProjects && enrichedProjects?.length > 0 && (
|
||||
<div className="mx-0 px-0 py-5 pb-0 md:-mx-10 md:px-8 md:py-8 md:pb-0">
|
||||
<h2 className="mb-3 text-xs uppercase text-gray-400">
|
||||
Your Projects
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
|
||||
{enrichedProjects.map((project) => (
|
||||
<ProjectProgress key={project._id} projectStatus={project} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasProgress && (
|
||||
<ActivityStream activities={activity?.activities || []} />
|
||||
)}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { getUser } from '../../lib/jwt';
|
||||
import { getPercentage } from '../../helper/number';
|
||||
import { ProjectProgressActions } from './ProjectProgressActions';
|
||||
import { cn } from '../../lib/classname';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||
import { ProjectStatus } from './ProjectStatus';
|
||||
import { ThumbsUp } from 'lucide-react';
|
||||
|
||||
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 shouldShowActions =
|
||||
projectStatus.submittedAt &&
|
||||
projectStatus.submittedAt !== null &&
|
||||
showActions;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<a
|
||||
className={cn(
|
||||
'group relative flex w-full items-center justify-between overflow-hidden rounded-md border border-gray-300 bg-white px-3 py-2 pr-7 text-left text-sm transition-all hover:border-gray-400',
|
||||
shouldShowActions ? '' : 'pr-3',
|
||||
)}
|
||||
href={`/projects/${projectStatus.projectId}`}
|
||||
target="_blank"
|
||||
>
|
||||
<ProjectStatus projectStatus={projectStatus} />
|
||||
<span className="ml-2 flex-grow truncate">{projectStatus?.title}</span>
|
||||
<span className="inline-flex items-center gap-1 text-xs text-gray-400">
|
||||
{projectStatus.upvotes}
|
||||
<ThumbsUp className="size-2.5 stroke-[2.5px]" />
|
||||
</span>
|
||||
</a>
|
||||
|
||||
{shouldShowActions && (
|
||||
<div className="absolute right-2 top-0 flex h-full items-center">
|
||||
<ProjectProgressActions
|
||||
userId={defaultUserId!}
|
||||
projectId={projectStatus.projectId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { MoreVertical, X } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { useKeydown } from '../../hooks/use-keydown';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { useCopyText } from '../../hooks/use-copy-text';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
import { ShareIcon } from '../ReactIcons/ShareIcon';
|
||||
|
||||
type ProjectProgressActionsType = {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export function ProjectProgressActions(props: ProjectProgressActionsType) {
|
||||
const { userId, projectId } = props;
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { copyText, isCopied } = useCopyText();
|
||||
|
||||
const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${userId}`;
|
||||
|
||||
useOutsideClick(dropdownRef, () => {
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
||||
useKeydown('Escape', () => {
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="relative h-full" ref={dropdownRef}>
|
||||
<button
|
||||
className="h-full text-gray-400 hover:text-gray-700"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute right-0 top-8 z-10 w-48 overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg">
|
||||
<button
|
||||
className={cn(
|
||||
'flex w-full items-center gap-1.5 p-2 text-xs font-medium hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-70 sm:text-sm',
|
||||
isCopied ? 'text-green-500' : 'text-gray-500 hover:text-black',
|
||||
)}
|
||||
onClick={() => {
|
||||
copyText(projectSolutionUrl);
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckIcon additionalClasses="h-3.5 w-3.5" /> Link Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ShareIcon className="h-3.5 w-3.5 stroke-[2.5px]" /> Share
|
||||
Solution
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { CircleDashed } from 'lucide-react';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon';
|
||||
|
||||
type ProjectStatusType = {
|
||||
projectStatus: ProjectStatusDocument & {
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function ProjectStatus(props: ProjectStatusType) {
|
||||
const { projectStatus } = props;
|
||||
|
||||
const { submittedAt, repositoryUrl } = projectStatus;
|
||||
const status = submittedAt && repositoryUrl ? 'submitted' : 'started';
|
||||
|
||||
if (status === 'submitted') {
|
||||
return <CheckIcon additionalClasses="size-3 text-gray-500 shrink-0" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CircleDashed className="size-3 shrink-0 stroke-[2.5px] text-gray-400" />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -48,7 +48,6 @@ function handleGuest() {
|
||||
'/team/members',
|
||||
'/team/member',
|
||||
'/team/settings',
|
||||
'/dashboard',
|
||||
];
|
||||
|
||||
showHideAuthElements('hide');
|
||||
|
||||
@@ -9,31 +9,28 @@ export function ContentConfirmationModal(props: ContentConfirmationModalProps) {
|
||||
const { onClose, onClick } = props;
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} wrapperClassName="max-w-lg">
|
||||
<Modal onClose={onClose}>
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold">
|
||||
Copy Node Details and Resources?
|
||||
</h2>
|
||||
<h2 className="text-lg font-semibold">Roadmap Content</h2>
|
||||
<p className="balanc text-gray-600">
|
||||
This will just copy the roadmap in your team. Would you like to copy
|
||||
the resource links and node details as well?
|
||||
Do you want to copy the content of this roadmap?
|
||||
</p>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
className="rounded-lg border p-2.5 font-normal"
|
||||
className="rounded-lg border p-2.5 font-medium"
|
||||
onClick={() => {
|
||||
onClick(false);
|
||||
}}
|
||||
>
|
||||
No, copy roadmap only
|
||||
No
|
||||
</button>
|
||||
<button
|
||||
className="rounded-lg border bg-black p-2.5 font-normal text-white hover:opacity-80"
|
||||
className="rounded-lg border bg-black p-2.5 font-medium text-white hover:opacity-80"
|
||||
onClick={() => {
|
||||
onClick(true);
|
||||
}}
|
||||
>
|
||||
Yes, also copy resources
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,94 +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 {
|
||||
ArrowUpRight,
|
||||
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 (
|
||||
<>
|
||||
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
|
||||
<h2 className="text-xs uppercase text-gray-400">
|
||||
AI Generated Roadmaps
|
||||
</h2>
|
||||
|
||||
{!isLoading && roadmaps.length !== 0 && (
|
||||
<a
|
||||
href="/ai/explore"
|
||||
className="flex items-center gap-1 text-xs text-gray-500 hover:text-black"
|
||||
>
|
||||
<ArrowUpRight size={12} />
|
||||
AI Generated Roadmaps
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!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 truncate rounded-md border bg-white p-2.5 text-left text-sm shadow-sm 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" />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +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}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
|
||||
type EmptyStackMessageProps = {
|
||||
number: number | string;
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
buttonLink: string;
|
||||
bodyClassName?: string;
|
||||
};
|
||||
|
||||
export function EmptyStackMessage(props: EmptyStackMessageProps) {
|
||||
const { number, title, description, buttonText, buttonLink, bodyClassName } =
|
||||
props;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-black/50">
|
||||
<div
|
||||
className={cn(
|
||||
'flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm',
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -1,130 +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 {
|
||||
ArrowUpRight,
|
||||
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}
|
||||
|
||||
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
|
||||
<h2 className="text-xs uppercase text-gray-400">
|
||||
{isAIGeneratedRoadmaps ? 'AI Generated Roadmaps' : 'Custom Roadmaps'}
|
||||
</h2>
|
||||
|
||||
{!isLoading && progresses.length !== 0 && (
|
||||
<a
|
||||
href="/ai/explore"
|
||||
className="flex items-center gap-1 text-xs text-gray-500 hover:text-black"
|
||||
>
|
||||
<ArrowUpRight size={12} />
|
||||
Community Roadmaps
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!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" />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,349 +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 font-medium bg-gray-200 hover:bg-gray-300 px-2.5 py-1 rounded-full text-gray-700 hover:text-black"
|
||||
>
|
||||
Visit Homepage
|
||||
</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 the 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}
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,355 +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 = 11;
|
||||
const MAX_PROJECTS_TO_SHOW = 8;
|
||||
|
||||
type ProgressLaneProps = {
|
||||
title: string;
|
||||
linkText?: string;
|
||||
linkHref?: string;
|
||||
isLoading?: boolean;
|
||||
isEmpty?: boolean;
|
||||
loadingWrapperClassName?: string;
|
||||
loadingSkeletonCount?: number;
|
||||
loadingSkeletonClassName?: string;
|
||||
children: React.ReactNode;
|
||||
emptyMessage?: string;
|
||||
emptyIcon?: LucideIcon;
|
||||
emptyLinkText?: string;
|
||||
emptyLinkHref?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ProgressLane(props: ProgressLaneProps) {
|
||||
const {
|
||||
title,
|
||||
linkText,
|
||||
linkHref,
|
||||
isLoading = false,
|
||||
loadingWrapperClassName = '',
|
||||
loadingSkeletonCount = 4,
|
||||
loadingSkeletonClassName = '',
|
||||
children,
|
||||
isEmpty = false,
|
||||
emptyIcon: EmptyIcon = Map,
|
||||
emptyMessage = `No ${title.toLowerCase()} to show`,
|
||||
emptyLinkHref = '/roadmaps',
|
||||
emptyLinkText = 'Explore',
|
||||
className,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{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 && (
|
||||
<div
|
||||
className={cn('grid grid-cols-2 gap-2', loadingWrapperClassName)}
|
||||
>
|
||||
{Array.from({ length: loadingSkeletonCount }).map((_, index) => (
|
||||
<CardSkeleton key={index} className={loadingSkeletonClassName} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!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 [showAllProgresses, setShowAllProgresses] = useState(false);
|
||||
const sortedProgresses = progresses.sort((a, b) => {
|
||||
if (a.isFavorite && !b.isFavorite) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!a.isFavorite && b.isFavorite) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
const userProgressesToShow = showAllProgresses
|
||||
? sortedProgresses
|
||||
: sortedProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
|
||||
|
||||
const [showAllProjects, setShowAllProjects] = useState(false);
|
||||
const projectsToShow = showAllProjects
|
||||
? projects
|
||||
: projects.slice(0, MAX_PROJECTS_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 col-span-2">
|
||||
{!isLoading && userProgressesToShow.length === 0 && (
|
||||
<EmptyStackMessage
|
||||
number={1}
|
||||
title={'Bookmark some Roadmaps'}
|
||||
description={
|
||||
'Bookmark some roadmaps to access them quickly and start updating your progress'
|
||||
}
|
||||
buttonText={'Explore Roadmaps'}
|
||||
buttonLink={'/roadmaps'}
|
||||
bodyClassName="max-w-[280px]"
|
||||
/>
|
||||
)}
|
||||
|
||||
<ProgressLane
|
||||
title="Progress & Bookmarks"
|
||||
isLoading={isLoading}
|
||||
loadingSkeletonCount={MAX_PROGRESS_TO_SHOW}
|
||||
linkHref="/roadmaps"
|
||||
linkText="Roadmaps"
|
||||
isEmpty={userProgressesToShow.length === 0}
|
||||
emptyIcon={Bookmark}
|
||||
emptyMessage={'No bookmarks to show'}
|
||||
emptyLinkHref={'/roadmaps'}
|
||||
emptyLinkText={'Explore Roadmaps'}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{userProgressesToShow.length > 0 && (
|
||||
<>
|
||||
{userProgressesToShow.map((progress) => {
|
||||
const isFavorite =
|
||||
progress.isFavorite &&
|
||||
!progress.done &&
|
||||
!progress.skipped;
|
||||
|
||||
if (isFavorite) {
|
||||
return (
|
||||
<DashboardBookmarkCard
|
||||
key={progress.resourceId}
|
||||
bookmark={progress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardProgressCard
|
||||
key={progress.resourceId}
|
||||
progress={progress}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{sortedProgresses.length > MAX_PROGRESS_TO_SHOW && (
|
||||
<ShowAllButton
|
||||
showAll={showAllProgresses}
|
||||
setShowAll={setShowAllProgresses}
|
||||
count={sortedProgresses.length}
|
||||
maxCount={MAX_PROGRESS_TO_SHOW}
|
||||
className="min-h-[38px] rounded-md border border-dashed leading-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ProgressLane>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<ProgressLane
|
||||
title={'Projects'}
|
||||
linkHref={'/projects'}
|
||||
linkText={'Projects'}
|
||||
isLoading={isLoading}
|
||||
loadingWrapperClassName="grid-cols-1"
|
||||
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={2}
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
type CardSkeletonProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function CardSkeleton(props: CardSkeletonProps) {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'h-[38px] 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>
|
||||
);
|
||||
}
|
||||
@@ -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" />
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'>·</span>
|
||||
<a href='/privacy' class='hover:text-white'>Privacy</a>
|
||||
<span class='mx-1.5'>·</span>
|
||||
<a href='/advertise' class='hover:text-white'>Advertise</a>
|
||||
<span class='mx-1.5'>·</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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import Icon from '../AstroIcon.astro';
|
||||
import { NavigationDropdown } from '../NavigationDropdown';
|
||||
import { AccountDropdown } from './AccountDropdown';
|
||||
import NewIndicator from './NewIndicator.astro';
|
||||
import { AccountStreak } from '../AccountStreak/AccountStreak';
|
||||
import { RoadmapDropdownMenu } from '../RoadmapDropdownMenu/RoadmapDropdownMenu';
|
||||
---
|
||||
|
||||
@@ -43,7 +42,10 @@ import { RoadmapDropdownMenu } from '../RoadmapDropdownMenu/RoadmapDropdownMenu'
|
||||
Start Here
|
||||
</a>
|
||||
<RoadmapDropdownMenu client:load />
|
||||
<a href='/teams' class='group relative text-gray-400 hover:text-white'>
|
||||
<a
|
||||
href='/teams'
|
||||
class='group relative !mr-5 text-gray-400 hover:text-white'
|
||||
>
|
||||
Teams
|
||||
</a>
|
||||
</div>
|
||||
@@ -53,8 +55,7 @@ import { RoadmapDropdownMenu } from '../RoadmapDropdownMenu/RoadmapDropdownMenu'
|
||||
<li data-guest-required class='hidden'>
|
||||
<a href='/login' class='text-gray-400 hover:text-white'>Login</a>
|
||||
</li>
|
||||
<li class='flex items-center gap-2'>
|
||||
<AccountStreak client:only='react' />
|
||||
<li>
|
||||
<AccountDropdown client:only='react' />
|
||||
|
||||
<a
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -8,7 +8,7 @@ export function EmptySolutions(props: EmptySolutionsProps) {
|
||||
const { projectId } = props;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[250px] flex-col items-center justify-center rounded-xl px-5 py-3 sm:px-0 sm:py-20 bg-white border mb-5">
|
||||
<div className="flex min-h-[250px] flex-col items-center justify-center rounded-xl px-5 py-3 sm:px-0 sm:py-20">
|
||||
<Blocks className="mb-4 opacity-10 h-14 w-14" />
|
||||
<h2 className="mb-1 text-lg font-semibold sm:text-xl">
|
||||
No solutions submitted yet
|
||||
|
||||
@@ -4,13 +4,13 @@ import { SubmissionRequirement } from './SubmissionRequirement.tsx';
|
||||
|
||||
type LeavingRoadmapWarningModalProps = {
|
||||
onClose: () => void;
|
||||
repositoryUrl: string;
|
||||
onContinue: () => void;
|
||||
};
|
||||
|
||||
export function LeavingRoadmapWarningModal(
|
||||
props: LeavingRoadmapWarningModalProps,
|
||||
) {
|
||||
const { onClose, repositoryUrl } = props;
|
||||
const { onClose, onContinue } = props;
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto p-4">
|
||||
@@ -45,14 +45,13 @@ export function LeavingRoadmapWarningModal(
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
<button
|
||||
className="inline-flex w-full items-center gap-2 rounded-lg bg-black px-3 py-2.5 text-sm text-white"
|
||||
href={repositoryUrl}
|
||||
target="_blank"
|
||||
onClick={onContinue}
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
Continue to Solution
|
||||
</a>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="absolute right-2.5 top-2.5 text-gray-600 hover:text-black"
|
||||
|
||||
@@ -13,9 +13,7 @@ import { isLoggedIn } from '../../lib/jwt';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import { VoteButton } from './VoteButton.tsx';
|
||||
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
|
||||
import { SelectLanguages } from './SelectLanguages.tsx';
|
||||
import type { ProjectFrontmatter } from '../../lib/project.ts';
|
||||
import { ProjectSolutionModal } from './ProjectSolutionModal.tsx';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
|
||||
export interface ProjectStatusDocument {
|
||||
_id?: string;
|
||||
@@ -26,14 +24,13 @@ export interface ProjectStatusDocument {
|
||||
startedAt?: Date;
|
||||
submittedAt?: Date;
|
||||
repositoryUrl?: string;
|
||||
languages?: string[];
|
||||
|
||||
upvotes: number;
|
||||
downvotes: number;
|
||||
|
||||
isVisible?: boolean;
|
||||
|
||||
updatedAt: Date;
|
||||
updated1t: Date;
|
||||
}
|
||||
|
||||
const allowedVoteType = ['upvote', 'downvote'] as const;
|
||||
@@ -56,20 +53,19 @@ type ListProjectSolutionsResponse = {
|
||||
|
||||
type QueryParams = {
|
||||
p?: string;
|
||||
l?: string;
|
||||
};
|
||||
|
||||
type PageState = {
|
||||
currentPage: number;
|
||||
language: string;
|
||||
};
|
||||
|
||||
const VISITED_SOLUTIONS_KEY = 'visited-project-solutions';
|
||||
|
||||
type ListProjectSolutionsProps = {
|
||||
project: ProjectFrontmatter;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const submittedAlternatives = [
|
||||
const submittedAlternatives = [
|
||||
'submitted their solution',
|
||||
'got it done',
|
||||
'submitted their take',
|
||||
@@ -94,26 +90,27 @@ export const submittedAlternatives = [
|
||||
];
|
||||
|
||||
export function ListProjectSolutions(props: ListProjectSolutionsProps) {
|
||||
const { projectId, project: projectData } = props;
|
||||
const { projectId } = props;
|
||||
|
||||
const toast = useToast();
|
||||
const [pageState, setPageState] = useState<PageState>({
|
||||
currentPage: 0,
|
||||
language: '',
|
||||
});
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [solutions, setSolutions] = useState<ListProjectSolutionsResponse>();
|
||||
const [alreadyVisitedSolutions, setAlreadyVisitedSolutions] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
const [showLeavingRoadmapModal, setShowLeavingRoadmapModal] = useState<
|
||||
ListProjectSolutionsResponse['data'][number] | null
|
||||
>(null);
|
||||
|
||||
const loadSolutions = async (page = 1, language: string = '') => {
|
||||
const loadSolutions = async (page = 1) => {
|
||||
const { response, error } = await httpGet<ListProjectSolutionsResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-project-solutions/${projectId}`,
|
||||
{
|
||||
currPage: page,
|
||||
...(language ? { languages: language } : {}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -135,7 +132,7 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
pageProgressMessage.set('Submitting vote');
|
||||
pageProgressMessage.set('Submitting vote...');
|
||||
const { response, error } = await httpPost(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-vote-project/${solutionId}`,
|
||||
{
|
||||
@@ -175,9 +172,13 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
|
||||
|
||||
useEffect(() => {
|
||||
const queryParams = getUrlParams() as QueryParams;
|
||||
const alreadyVisitedSolutions = JSON.parse(
|
||||
localStorage.getItem(VISITED_SOLUTIONS_KEY) || '{}',
|
||||
);
|
||||
|
||||
setAlreadyVisitedSolutions(alreadyVisitedSolutions);
|
||||
setPageState({
|
||||
currentPage: +(queryParams.p || '1'),
|
||||
language: queryParams.l || '',
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -187,21 +188,23 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageState.currentPage !== 1 || pageState.language !== '') {
|
||||
if (pageState.currentPage !== 1) {
|
||||
setUrlParams({
|
||||
p: String(pageState.currentPage),
|
||||
l: pageState.language,
|
||||
});
|
||||
} else {
|
||||
deleteUrlParam('p');
|
||||
deleteUrlParam('l');
|
||||
}
|
||||
|
||||
loadSolutions(pageState.currentPage, pageState.language).finally(() => {
|
||||
loadSolutions(pageState.currentPage).finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [pageState]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSolutions />;
|
||||
}
|
||||
|
||||
const isEmpty = solutions?.data.length === 0;
|
||||
if (isEmpty) {
|
||||
return <EmptySolutions projectId={projectId} />;
|
||||
@@ -210,128 +213,116 @@ export function ListProjectSolutions(props: ListProjectSolutionsProps) {
|
||||
const leavingRoadmapModal = showLeavingRoadmapModal ? (
|
||||
<LeavingRoadmapWarningModal
|
||||
onClose={() => setShowLeavingRoadmapModal(null)}
|
||||
repositoryUrl={showLeavingRoadmapModal?.repositoryUrl!}
|
||||
onContinue={() => {
|
||||
const visitedSolutions = {
|
||||
...alreadyVisitedSolutions,
|
||||
[showLeavingRoadmapModal._id!]: true,
|
||||
};
|
||||
localStorage.setItem(
|
||||
VISITED_SOLUTIONS_KEY,
|
||||
JSON.stringify(visitedSolutions),
|
||||
);
|
||||
|
||||
window.open(showLeavingRoadmapModal.repositoryUrl, '_blank');
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const selectedLanguage = pageState.language;
|
||||
|
||||
return (
|
||||
<div className="mb-4 overflow-hidden rounded-lg border bg-white p-3 sm:p-5">
|
||||
<section>
|
||||
{leavingRoadmapModal}
|
||||
<div className="relative mb-5 hidden items-center justify-between sm:flex">
|
||||
<div>
|
||||
<h1 className="mb-1 text-xl font-semibold">
|
||||
{projectData.title} Solutions
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">{projectData.description}</p>
|
||||
</div>
|
||||
{!isLoading && (
|
||||
<SelectLanguages
|
||||
projectId={projectId}
|
||||
selectedLanguage={selectedLanguage}
|
||||
onSelectLanguage={(language) => {
|
||||
setPageState((prev) => ({
|
||||
...prev,
|
||||
language: prev.language === language ? '' : language,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex min-h-[500px] flex-col divide-y divide-gray-100">
|
||||
{solutions?.data.map((solution, counter) => {
|
||||
const isVisited = alreadyVisitedSolutions[solution._id!];
|
||||
const avatar = solution.user.avatar || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={solution._id}
|
||||
className={
|
||||
'flex flex-col justify-between gap-2 py-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0'
|
||||
}
|
||||
>
|
||||
<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[
|
||||
counter % 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-end gap-1">
|
||||
<span className="flex overflow-hidden rounded-full border">
|
||||
<VoteButton
|
||||
icon={ThumbsUp}
|
||||
isActive={solution?.voteType === 'upvote'}
|
||||
count={solution.upvotes || 0}
|
||||
onClick={() => {
|
||||
handleSubmitVote(solution._id!, 'upvote');
|
||||
}}
|
||||
/>
|
||||
|
||||
<VoteButton
|
||||
icon={ThumbsDown}
|
||||
isActive={solution?.voteType === 'downvote'}
|
||||
count={solution.downvotes || 0}
|
||||
hideCount={true}
|
||||
onClick={() => {
|
||||
handleSubmitVote(solution._id!, 'downvote');
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<a
|
||||
className="ml-1 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"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowLeavingRoadmapModal(solution);
|
||||
}}
|
||||
target="_blank"
|
||||
href={solution.repositoryUrl}
|
||||
>
|
||||
<GitHubIcon className="h-4 w-4 text-current" />
|
||||
Visit Solution
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingSolutions />
|
||||
) : (
|
||||
<>
|
||||
<div className="flex min-h-[500px] flex-col divide-y divide-gray-100">
|
||||
{solutions?.data.map((solution, counter) => {
|
||||
const avatar = solution.user.avatar || '';
|
||||
return (
|
||||
<div
|
||||
key={solution._id}
|
||||
className="flex flex-col gap-2 py-2 text-sm text-gray-500"
|
||||
>
|
||||
<div className="flex flex-col justify-between gap-2 text-sm text-gray-500 sm:flex-row sm:items-center sm:gap-0">
|
||||
<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[
|
||||
counter % 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-end gap-1">
|
||||
<span 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');
|
||||
}}
|
||||
/>
|
||||
|
||||
<VoteButton
|
||||
icon={ThumbsDown}
|
||||
isActive={solution?.voteType === 'downvote'}
|
||||
count={solution.downvotes || 0}
|
||||
hideCount={true}
|
||||
onClick={() => {
|
||||
handleSubmitVote(solution._id!, 'downvote');
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<button
|
||||
className="ml-1 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"
|
||||
onClick={() => {
|
||||
setShowLeavingRoadmapModal(solution);
|
||||
}}
|
||||
>
|
||||
<GitHubIcon className="h-4 w-4 text-current" />
|
||||
Visit Solution
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{(solutions?.totalPages || 0) > 1 && (
|
||||
<div className="mt-4">
|
||||
<Pagination
|
||||
totalPages={solutions?.totalPages || 1}
|
||||
currPage={solutions?.currPage || 1}
|
||||
perPage={solutions?.perPage || 21}
|
||||
totalCount={solutions?.totalCount || 0}
|
||||
onPageChange={(page) => {
|
||||
setPageState({
|
||||
...pageState,
|
||||
currentPage: page,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
{(solutions?.totalPages || 0) > 1 && (
|
||||
<div className="mt-4">
|
||||
<Pagination
|
||||
totalPages={solutions?.totalPages || 1}
|
||||
currPage={solutions?.currPage || 1}
|
||||
perPage={solutions?.perPage || 21}
|
||||
totalCount={solutions?.totalCount || 0}
|
||||
onPageChange={(page) => {
|
||||
setPageState({
|
||||
...pageState,
|
||||
currentPage: page,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { deleteUrlParam, getUrlParams } from '../../lib/browser';
|
||||
import { ModalLoader } from '../UserProgress/ModalLoader';
|
||||
import { Modal } from '../Modal';
|
||||
import { httpGet, httpPost } from '../../lib/http';
|
||||
import {
|
||||
submittedAlternatives,
|
||||
type AllowedVoteType,
|
||||
} from './ListProjectSolutions';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
import { ArrowUpRight, ThumbsDown, ThumbsUp, Trophy } from 'lucide-react';
|
||||
import { VoteButton } from './VoteButton';
|
||||
import { GitHubIcon } from '../ReactIcons/GitHubIcon';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { showLoginPopup } from '../../lib/popup';
|
||||
import { pageProgressMessage } from '../../stores/page';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
|
||||
type UserProjectSolutionResponse = {
|
||||
id?: string;
|
||||
|
||||
startedAt?: Date;
|
||||
submittedAt?: Date;
|
||||
repositoryUrl?: string;
|
||||
|
||||
upvotes?: number;
|
||||
downvotes?: number;
|
||||
|
||||
voteType?: AllowedVoteType | 'none';
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ProjectSolutionModalProps = {
|
||||
projectId: string;
|
||||
projectTitle: string;
|
||||
projectDescription: string;
|
||||
};
|
||||
|
||||
export function ProjectSolutionModal(props: ProjectSolutionModalProps) {
|
||||
const { projectId, projectTitle, projectDescription } = props;
|
||||
|
||||
const { u: userId } = getUrlParams();
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toast = useToast();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [solution, setSolution] = useState<UserProjectSolutionResponse>();
|
||||
|
||||
const loadUserProjectSolution = async () => {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
const { response, error } = await httpGet<UserProjectSolutionResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-project-solution/${projectId}/${userId}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
setError(error?.message || 'Something went wrong');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setSolution(response);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleSubmitVote = async (
|
||||
solutionId: string,
|
||||
voteType: AllowedVoteType,
|
||||
) => {
|
||||
if (!isLoggedIn()) {
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
pageProgressMessage.set('Submitting vote');
|
||||
const { response, error } = await httpPost(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-vote-project/${solutionId}`,
|
||||
{
|
||||
voteType,
|
||||
},
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Failed to submit vote');
|
||||
pageProgressMessage.set('');
|
||||
return;
|
||||
}
|
||||
|
||||
pageProgressMessage.set('');
|
||||
setSolution((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
upvotes: response?.upvotes || 0,
|
||||
downvotes: response?.downvotes || 0,
|
||||
voteType,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUserProjectSolution().finally();
|
||||
}, []);
|
||||
|
||||
if (isLoading || error) {
|
||||
return (
|
||||
<ModalLoader
|
||||
text="Loading project solution..."
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const avatar = solution?.user.avatar;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={() => {
|
||||
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="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>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<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"
|
||||
href={solution?.repositoryUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<GitHubIcon className="h-5 w-5 text-current" />
|
||||
View Solution on GitHub
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
</a>
|
||||
|
||||
<div className="flex overflow-hidden rounded-full border">
|
||||
<VoteButton
|
||||
icon={ThumbsUp}
|
||||
isActive={solution?.voteType === 'upvote'}
|
||||
count={solution?.upvotes || 0}
|
||||
onClick={() => handleSubmitVote(solution?.id!, 'upvote')}
|
||||
/>
|
||||
<VoteButton
|
||||
icon={ThumbsDown}
|
||||
isActive={solution?.voteType === 'downvote'}
|
||||
count={solution?.downvotes || 0}
|
||||
hideCount={true}
|
||||
onClick={() => handleSubmitVote(solution?.id!, 'downvote')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Blocks,
|
||||
BoxSelect,
|
||||
type LucideIcon,
|
||||
StepBackIcon,
|
||||
StickyNote,
|
||||
Text,
|
||||
} from 'lucide-react';
|
||||
@@ -36,7 +34,7 @@ function TabButton(props: TabButtonProps) {
|
||||
{smText && <span className="sm:hidden">{smText}</span>}
|
||||
|
||||
{isActive && (
|
||||
<span className="absolute bottom-0 left-0 right-0 h-0.5 translate-y-1/2 rounded-t-md bg-black"></span>
|
||||
<span className="absolute bottom-0 left-0 right-0 h-0.5 translate-y-1/2 bg-black rounded-t-md"></span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
@@ -45,23 +43,13 @@ function TabButton(props: TabButtonProps) {
|
||||
type ProjectTabsProps = {
|
||||
activeTab: AllowedProjectTab;
|
||||
projectId: string;
|
||||
parentRoadmapId?: string;
|
||||
};
|
||||
|
||||
export function ProjectTabs(props: ProjectTabsProps) {
|
||||
const { activeTab, parentRoadmapId, projectId } = props;
|
||||
const { activeTab, projectId } = props;
|
||||
|
||||
return (
|
||||
<div className="my-3 flex flex-row flex-wrap items-center gap-1.5 overflow-hidden rounded-md border bg-white px-2.5 text-sm">
|
||||
<a
|
||||
href={`/${parentRoadmapId}/projects`}
|
||||
className={
|
||||
'-ml-1.5 flex items-center rounded-md bg-gray-300 px-2 py-1.5 text-xs tracking-wide text-black hover:bg-gray-400/60'
|
||||
}
|
||||
>
|
||||
<ArrowLeft className="mr-1 inline-block h-3.5 w-3.5" strokeWidth={2} />
|
||||
<span className="hidden sm:inline">Back to Projects</span>
|
||||
</a>
|
||||
<div className="my-3 flex flex-row flex-wrap items-center gap-1.5 rounded-md border bg-white px-2.5 text-sm">
|
||||
<TabButton
|
||||
text={'Project Detail'}
|
||||
icon={Text}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { ChevronDown, X } from 'lucide-react';
|
||||
|
||||
type SelectLanguagesProps = {
|
||||
projectId: string;
|
||||
selectedLanguage: string;
|
||||
onSelectLanguage: (language: string) => void;
|
||||
};
|
||||
|
||||
export function SelectLanguages(props: SelectLanguagesProps) {
|
||||
const { projectId, onSelectLanguage, selectedLanguage } = props;
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const toast = useToast();
|
||||
|
||||
const [distinctLanguages, setDistinctLanguages] = useState<string[]>([]);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const loadDistinctLanguages = async () => {
|
||||
const { response, error } = await httpGet<string[]>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-project-languages/${projectId}`,
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Failed to load project languages');
|
||||
return;
|
||||
}
|
||||
|
||||
setDistinctLanguages(response);
|
||||
};
|
||||
|
||||
useOutsideClick(dropdownRef, () => {
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadDistinctLanguages().finally(() => {});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative flex">
|
||||
<button
|
||||
className="flex items-center gap-1 rounded-md border border-gray-300 py-1.5 pl-3 pr-2 text-xs font-medium text-gray-900"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{selectedLanguage || 'Select Language'}
|
||||
|
||||
<ChevronDown className="ml-1 h-4 w-4" />
|
||||
</button>
|
||||
{selectedLanguage && (
|
||||
<button
|
||||
className="ml-1 text-red-500 text-xs border border-red-500 rounded-md px-2 py-1"
|
||||
onClick={() => onSelectLanguage('')}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute right-0 top-full z-10 w-full min-w-[200px] max-w-[200px] translate-y-1.5 overflow-hidden rounded-md border border-gray-300 bg-white p-1 shadow-lg"
|
||||
ref={dropdownRef}
|
||||
>
|
||||
{distinctLanguages.map((language) => {
|
||||
const isSelected = selectedLanguage === language;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={language}
|
||||
className="flex w-full items-center rounded-md px-4 py-1.5 text-left text-sm text-gray-700 hover:bg-gray-100 aria-selected:bg-gray-100"
|
||||
onClick={() => {
|
||||
onSelectLanguage(language);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
{language}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Flag, Play, Send, Share, Square, StopCircle, X } from 'lucide-react';
|
||||
import { Flag, Play, Send } 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';
|
||||
import { isLoggedIn } from '../../../lib/jwt.ts';
|
||||
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;
|
||||
@@ -35,13 +32,9 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
|
||||
const stickyElRef = useRef<HTMLDivElement>(null);
|
||||
const isSticky = useStickyStuck(stickyElRef, 8);
|
||||
const currentUser = getUser();
|
||||
|
||||
const [isStartingProject, setIsStartingProject] = useState(false);
|
||||
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);
|
||||
@@ -81,41 +74,17 @@ 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(() => {});
|
||||
}, []);
|
||||
|
||||
const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${currentUser?.id}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={stickyElRef}
|
||||
className={cn(
|
||||
'relative top-0 -mx-4 my-5 overflow-hidden rounded-none border border-x-0 bg-white transition-all sm:sticky sm:mx-0 sm:rounded-lg sm:border-x',
|
||||
'relative sm:sticky top-0 my-5 -mx-4 sm:mx-0 overflow-hidden rounded-none border-x-0 sm:border-x sm:rounded-lg border bg-white transition-all',
|
||||
{
|
||||
'sm:-mx-5 sm:rounded-none sm:border-x-0 sm:border-t-0 sm:bg-gray-50':
|
||||
isSticky,
|
||||
'sm:-mx-5 sm:rounded-none sm:border-x-0 sm:border-t-0 sm:bg-gray-50': isSticky,
|
||||
},
|
||||
)}
|
||||
>
|
||||
@@ -162,7 +131,7 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'bg-gray-100 px-4 py-2 text-sm text-gray-500 transition-colors sm:flex sm:items-center',
|
||||
'px-4 py-2 text-sm text-gray-500 transition-colors bg-gray-100',
|
||||
{
|
||||
'bg-purple-600 text-white': isSticky,
|
||||
},
|
||||
@@ -175,7 +144,7 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
)}
|
||||
{activeStep === 1 && (
|
||||
<>
|
||||
Started working
|
||||
Started working{' '}
|
||||
<span
|
||||
className={cn('font-medium text-gray-800', {
|
||||
'text-purple-200': isSticky,
|
||||
@@ -183,7 +152,7 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
>
|
||||
{getRelativeTimeString(projectStatus.startedAt!)}
|
||||
</span>
|
||||
. Follow
|
||||
. Follow{' '}
|
||||
<button
|
||||
className={cn('underline underline-offset-2 hover:text-black', {
|
||||
'text-purple-100 hover:text-white': isSticky,
|
||||
@@ -193,13 +162,13 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
}}
|
||||
>
|
||||
these tips
|
||||
</button>
|
||||
to get most out of it.
|
||||
</button>{' '}
|
||||
to get most out of it.
|
||||
</>
|
||||
)}
|
||||
{activeStep >= 2 && (
|
||||
<>
|
||||
Congrats on submitting your solution.
|
||||
Congrats on submitting your solution.{' '}
|
||||
<button
|
||||
className={cn('underline underline-offset-2 hover:text-black', {
|
||||
'text-purple-100 hover:text-white': isSticky,
|
||||
@@ -212,48 +181,9 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{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(
|
||||
'ml-auto hidden items-center gap-1 text-sm sm:flex',
|
||||
isCopied ? 'text-green-500' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
copyText(projectSolutionUrl);
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckIcon additionalClasses="h-3 w-3" />
|
||||
Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Share className="h-3 w-3 stroke-[2.5px]" />
|
||||
<span className="hidden md:inline">Share your Solution</span>
|
||||
<span className="md:hidden">Share</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-[60px] flex-col items-start justify-between gap-2 px-4 py-4 sm:flex-row sm:items-center sm:gap-3 sm:py-0">
|
||||
<div className="flex flex-col sm:flex-row min-h-[60px] items-start sm:items-center justify-between gap-2 sm:gap-3 px-4 py-4 sm:py-0">
|
||||
<StepperAction
|
||||
isActive={activeStep === 0}
|
||||
isCompleted={activeStep > 0}
|
||||
@@ -270,46 +200,21 @@ export function ProjectStepper(props: ProjectStepperProps) {
|
||||
}}
|
||||
/>
|
||||
<StepperStepSeparator isActive={activeStep > 0} />
|
||||
<div className="flex items-center gap-2">
|
||||
<StepperAction
|
||||
isActive={activeStep === 1}
|
||||
isCompleted={activeStep > 1}
|
||||
icon={Send}
|
||||
onClick={() => {
|
||||
if (!isLoggedIn()) {
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
<StepperAction
|
||||
isActive={activeStep === 1}
|
||||
isCompleted={activeStep > 1}
|
||||
icon={Send}
|
||||
onClick={() => {
|
||||
if (!isLoggedIn()) {
|
||||
showLoginPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmittingProject(true);
|
||||
}}
|
||||
text={activeStep > 1 ? 'Submitted' : 'Submit Solution'}
|
||||
number={2}
|
||||
/>
|
||||
|
||||
<span className="text-gray-600 sm:hidden">·</span>
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-2 text-sm sm:hidden',
|
||||
isCopied ? 'text-green-500' : 'text-gray-600',
|
||||
)}
|
||||
onClick={() => {
|
||||
copyText(projectSolutionUrl);
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckIcon additionalClasses="h-3 w-3" />
|
||||
URL Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Share className="h-3 w-3 stroke-[2.5px]" />
|
||||
Share your Solution
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
setIsSubmittingProject(true);
|
||||
}}
|
||||
text={activeStep > 1 ? 'Submitted' : 'Submit Solution'}
|
||||
number={2}
|
||||
/>
|
||||
<StepperStepSeparator isActive={activeStep > 1} />
|
||||
<MilestoneStep
|
||||
isActive={activeStep === 2}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
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';
|
||||
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
|
||||
import { SubmissionRequirement } from './SubmissionRequirement.tsx';
|
||||
import { useCopyText } from '../../hooks/use-copy-text.ts';
|
||||
import { getTopGitHubLanguages } from '../../lib/github.ts';
|
||||
import { SubmitSuccessModal } from './SubmitSuccessModal.tsx';
|
||||
|
||||
type SubmitProjectResponse = {
|
||||
repositoryUrl: string;
|
||||
submittedAt: Date;
|
||||
};
|
||||
|
||||
type GitHubApiLanguagesResponse = Record<string, number>;
|
||||
|
||||
type VerificationChecksType = {
|
||||
repositoryExists: 'pending' | 'success' | 'error';
|
||||
readmeExists: 'pending' | 'success' | 'error';
|
||||
@@ -39,7 +36,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 +58,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
|
||||
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
setIsSuccess(false);
|
||||
setSuccessMessage('');
|
||||
|
||||
if (!repoUrl) {
|
||||
setVerificationChecks({
|
||||
@@ -173,23 +170,10 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
|
||||
projectUrlExists: 'success',
|
||||
});
|
||||
|
||||
const languagesResponse = await fetch(`${mainApiUrl}/languages`);
|
||||
let languages: string[] = [];
|
||||
if (languagesResponse.ok) {
|
||||
const languagesData =
|
||||
(await languagesResponse.json()) as GitHubApiLanguagesResponse;
|
||||
|
||||
languages = getTopGitHubLanguages(languagesData);
|
||||
if (languages?.length === 0) {
|
||||
languages = Object.keys(languagesData || {})?.slice(0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
const submitProjectUrl = `${import.meta.env.PUBLIC_API_URL}/v1-submit-project/${projectId}`;
|
||||
const { response: submitResponse, error } =
|
||||
await httpPost<SubmitProjectResponse>(submitProjectUrl, {
|
||||
repositoryUrl: repoUrl,
|
||||
languages,
|
||||
});
|
||||
|
||||
if (error || !submitResponse) {
|
||||
@@ -198,7 +182,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
|
||||
);
|
||||
}
|
||||
|
||||
setIsSuccess(true);
|
||||
setSuccessMessage('Solution submitted successfully!');
|
||||
setIsLoading(false);
|
||||
|
||||
onSubmit(submitResponse);
|
||||
@@ -209,8 +193,15 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
if (isSuccess) {
|
||||
return <SubmitSuccessModal projectId={projectId} onClose={onClose} />;
|
||||
if (successMessage) {
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto p-4">
|
||||
<div className="flex flex-col items-center justify-center gap-4 pb-10 pt-12">
|
||||
<ReactCheckIcon additionalClasses={'h-12 text-green-500 w-12'} />
|
||||
<p className="text-lg font-medium">{successMessage}</p>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -281,7 +272,7 @@ export function SubmitProjectModal(props: SubmitProjectModalProps) {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="mt-2 w-full rounded-lg bg-black p-2 text-sm font-medium text-white disabled:opacity-50"
|
||||
className="mt-2 w-full rounded-lg bg-black p-2 font-medium text-white disabled:opacity-50 text-sm"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Verifying...' : 'Verify and Submit'}
|
||||
@@ -289,6 +280,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
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { CheckCircle, CheckCircle2, Clipboard, Copy } from 'lucide-react';
|
||||
import { getUser } from '../../lib/jwt.ts';
|
||||
import { Modal } from '../Modal';
|
||||
import { CheckIcon as ReactCheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
||||
import { useCopyText } from '../../hooks/use-copy-text.ts';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
|
||||
type SubmitSuccessModalProps = {
|
||||
projectId: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function SubmitSuccessModal(props: SubmitSuccessModalProps) {
|
||||
const { onClose, projectId } = props;
|
||||
|
||||
const user = getUser();
|
||||
|
||||
const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${user?.id}`;
|
||||
|
||||
const { isCopied, copyText } = useCopyText();
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} bodyClassName="h-auto p-4">
|
||||
<div className="flex flex-col items-center justify-center pb-3 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-0.5 text-center text-sm text-gray-500">
|
||||
Congrats! Your solution has been submitted.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 w-full">
|
||||
<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"
|
||||
onClick={(e) => {
|
||||
e.currentTarget.select();
|
||||
}}
|
||||
/>
|
||||
|
||||
<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'
|
||||
)}
|
||||
onClick={() => {
|
||||
copyText(projectSolutionUrl);
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckCircle className="size-4 stroke-[2.5px]" />
|
||||
URL Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="size-4 stroke-[2.5px]" />
|
||||
Copy Shareable Link
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { cn } from '../../lib/classname';
|
||||
import { decimalIfNeeded } from '../../lib/number.ts';
|
||||
|
||||
type RatingProps = {
|
||||
rating?: number;
|
||||
@@ -41,7 +40,7 @@ export function Rating(props: RatingProps) {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setStars(counter);
|
||||
onRatingChange?.(counter);
|
||||
}}
|
||||
@@ -50,14 +49,9 @@ export function Rating(props: RatingProps) {
|
||||
);
|
||||
})}
|
||||
{(props.total || 0) > 0 && (
|
||||
<>
|
||||
<span className="ml-1.5 text-xs font-medium text-gray-400">
|
||||
{decimalIfNeeded(Number(props.rating!))}
|
||||
</span>
|
||||
<span className="ml-1 text-xs text-gray-400">
|
||||
({Intl.NumberFormat('en-US').format(props.total!)})
|
||||
</span>
|
||||
</>
|
||||
<span className="ml-1.5 text-xs text-gray-400">
|
||||
({Intl.NumberFormat('en-US').format(props.total!)})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user