mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-15 03:18:46 +08:00
Compare commits
1 Commits
fix/sql
...
feat/pkg-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b22cb4e7a9 |
@@ -3,6 +3,6 @@
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1739229597159
|
||||
"lastUpdateCheck": 1738019390029
|
||||
}
|
||||
}
|
||||
2
.github/workflows/cloudfront-fe-cache.yml
vendored
2
.github/workflows/cloudfront-fe-cache.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
||||
-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,cloudfront-course", "is_verbose": false } }'
|
||||
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'
|
||||
|
||||
18
.github/workflows/greetings.yml
vendored
18
.github/workflows/greetings.yml
vendored
@@ -15,9 +15,19 @@ jobs:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-message: |
|
||||
Thank you for your first ever contribution to [roadmap.sh](https://roadmap.sh)! 🎉
|
||||
issue-message: |
|
||||
🙌 Hello! Thank you for taking the time to file an issue.
|
||||
|
||||
Please make sure to follow the [contribution guidelines](https://github.com/kamranahmedse/developer-roadmap/blob/master/contributing.md) when contributing to this project. Any PRs that don't follow the guidelines will be closed.
|
||||
If this is a bug report, please include any relevant logs or details that can help us debug the problem. Your help is greatly appreciated! 💡
|
||||
|
||||
Thanks for choosing to contribute, and for helping make this project better! 🌟
|
||||
We'll get back to you as soon as possible, kindly be patient for a response from a maintainer.
|
||||
pr-message: |
|
||||
🎉 Warm regards and welcome! Thank you for your first ever contribution to **Roadmap.sh**!
|
||||
|
||||
We appreciate your effort and enthusiasm. Before diving in, we kindly ask you to take a moment to go through our [Contribution Guidelines](https://github.com/kamranahmedse/developer-roadmap/blob/master/contributing.md) 📘 to ensure your contribution aligns with the project's standards and goals.
|
||||
|
||||
If you are fixing a bug, please reference the associated issue number in your pull request description. 🐛
|
||||
|
||||
If you're working on a new feature, feel free to check with the community on [discord](https://roadmap.sh/discord) to ensure the feature will be accepted. *Also, kindly refrain pinging the maintainer(s).* 🚀
|
||||
|
||||
Thanks for choosing to contribute, and for making this project better! 🌟
|
||||
@@ -1,10 +1,10 @@
|
||||
// https://astro.build/config
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import node from '@astrojs/node';
|
||||
import { defineConfig } from 'astro/config';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
@@ -46,15 +46,13 @@ export default defineConfig({
|
||||
}),
|
||||
trailingSlash: 'never',
|
||||
integrations: [
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false,
|
||||
},
|
||||
}),
|
||||
sitemap({
|
||||
filter: shouldIndexPage,
|
||||
serialize: serializeSitemap,
|
||||
}),
|
||||
react(),
|
||||
],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,12 +32,11 @@
|
||||
"@astrojs/node": "^8.3.4",
|
||||
"@astrojs/react": "^3.6.2",
|
||||
"@astrojs/sitemap": "^3.2.0",
|
||||
"@astrojs/tailwind": "^5.1.2",
|
||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
||||
"@microsoft/clarity": "^1.0.0",
|
||||
"@nanostores/react": "^0.8.0",
|
||||
"@napi-rs/image": "^1.9.2",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tanstack/react-query": "^5.59.16",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
@@ -74,14 +73,13 @@
|
||||
"sharp": "^0.33.5",
|
||||
"slugify": "^1.6.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"turndown": "^7.2.0",
|
||||
"unified": "^11.0.5",
|
||||
"zustand": "^4.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/google": "^1.1.19",
|
||||
"@playwright/test": "^1.48.0",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
@@ -92,7 +90,6 @@
|
||||
"@types/react-slick": "^0.23.13",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"ai": "^4.1.51",
|
||||
"csv-parser": "^3.0.0",
|
||||
"gh-pages": "^6.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -100,7 +97,7 @@
|
||||
"openai": "^4.67.3",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tsx": "^4.19.1"
|
||||
}
|
||||
}
|
||||
|
||||
1041
pnpm-lock.yaml
generated
1041
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,7 @@
|
||||
]
|
||||
},
|
||||
"mwPJh33MEUQ4Co_LiVEOb": {
|
||||
"title": "Differential Calculus ",
|
||||
"title": "Differential Calculus",
|
||||
"description": "",
|
||||
"links": [
|
||||
{
|
||||
@@ -330,17 +330,17 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Advantages and Disadvantages of AI",
|
||||
"url": "https://medium.com/@laners.org/advantages-and-disadvantages-of-artificial-intelligence-cd6e42819b20",
|
||||
"url": "https://towardsdatascience.com/advantages-and-disadvantages-of-artificial-intelligence-182a5ef6588c",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Reinforcement Learning 101",
|
||||
"url": "https://medium.com/towards-data-science/reinforcement-learning-101-e24b50e1d292",
|
||||
"url": "https://towardsdatascience.com/reinforcement-learning-101-e24b50e1d292",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Understanding AUC-ROC Curve",
|
||||
"url": "https://medium.com/towards-data-science/understanding-auc-roc-curve-68b2303cc9c5",
|
||||
"url": "https://towardsdatascience.com/understanding-auc-roc-curve-68b2303cc9c5",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -458,29 +458,8 @@
|
||||
},
|
||||
"O7wjldZ3yTA2s_F-UnJw_": {
|
||||
"title": "Rate Limiting",
|
||||
"description": "Rate Limiting is a critical aspect of API Design that dictates the number of API calls a client can make within a specified timeframe. This helps in managing resource allocation, preventing abuse of the API, and maintaining the overall health of the API system. Proper rate limiting measures should be in place to ensure the API's stability, thereby delivering a consistent and reliable service to all consumers. It works primarily by setting a limit on the frequency of client requests, thereby preventing individual users from overloading the system. It is crucial to design and implement rate limiting carefully for maintaining API availability and performance.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Rate limit",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Rate_limit",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Throttle",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Throttle",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Debounce",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Debounce",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What is rate limiting? | Rate limiting and bots",
|
||||
"url": "https://www.cloudflare.com/en-gb/learning/bots/what-is-rate-limiting/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "Rate Limiting is a critical aspect of API Design that dictates the number of API calls a client can make within a specified timeframe. This helps in managing resource allocation, preventing abuse of the API, and maintaining the overall health of the API system. Proper rate limiting measures should be in place to ensure the API's stability, thereby delivering a consistent and reliable service to all consumers. It works primarily by setting a limit on the frequency of client requests, thereby preventing individual users from overloading the system. It is crucial to design and implement rate limiting carefully for maintaining API availability and performance.",
|
||||
"links": []
|
||||
},
|
||||
"20KEgZH6cu_UokqWpV-9I": {
|
||||
"title": "Idempotency",
|
||||
@@ -590,11 +569,6 @@
|
||||
"title": "Caching REST API Response",
|
||||
"url": "https://restfulapi.net/caching/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "HTTP caching",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -735,7 +709,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "API Authorization Methods",
|
||||
"url": "https://www.pingidentity.com/en/resources/identity-fundamentals/authorization/authorization-methods.html",
|
||||
"url": "https://konghq.com/blog/engineering/common-api-authentication-methods",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -629,7 +629,7 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "MySQL Complete Course",
|
||||
"title": "MySQL Full Course for free",
|
||||
"url": "https://www.youtube.com/watch?v=5OdVJbNCSso",
|
||||
"type": "video"
|
||||
}
|
||||
@@ -1690,8 +1690,8 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "10 Common Software Architectural Patterns in a nutshell",
|
||||
"url": "https://theiotacademy.medium.com/10-common-software-architectural-patterns-in-a-nutshell-1b1f6cf5036b",
|
||||
"title": "Architectural Patterns in a nutshell",
|
||||
"url": "https://towardsdatascience.com/10-common-software-architectural-patterns-in-a-nutshell-a0b47a1e9013",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,12 +55,12 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Python",
|
||||
"title": "Python Website",
|
||||
"url": "https://www.python.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Getting Started with Python",
|
||||
"title": "Python Getting Started",
|
||||
"url": "https://www.python.org/about/gettingstarted/",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -182,11 +182,6 @@
|
||||
"title": "Rust",
|
||||
"description": "Rust is a modern systems programming language focusing on safety, speed, and concurrency. It accomplishes these goals by being memory safe without using garbage collection.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Rust Roadmap",
|
||||
"url": "https://roadmap.sh/rust",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "The Rust Programming Language - online book",
|
||||
"url": "https://doc.rust-lang.org/book/",
|
||||
@@ -218,11 +213,6 @@
|
||||
"title": "C++",
|
||||
"description": "C++ is a powerful general-purpose programming language. It can be used to develop operating systems, browsers, games, and so on. C++ supports different ways of programming like procedural, object-oriented, functional, and so on. This makes C++ powerful as well as flexible.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "C++ Roadmap",
|
||||
"url": "https://roadmap.sh/cpp",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Learn C++",
|
||||
"url": "https://learncpp.com/",
|
||||
@@ -248,6 +238,11 @@
|
||||
"url": "https://www.w3schools.com/cpp/default.asp",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "C++ Roadmap",
|
||||
"url": "https://roadmap.sh/cpp",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Explore top posts about C++ Programming",
|
||||
"url": "https://app.daily.dev/tags/c++?ref=roadmapsh",
|
||||
@@ -331,11 +326,6 @@
|
||||
"url": "https://techdevguide.withgoogle.com/paths/data-structures-and-algorithms/",
|
||||
"type": "course"
|
||||
},
|
||||
{
|
||||
"title": "Visit Dedicated DSA Roadmap",
|
||||
"url": "https://roadmap.sh/datastructures-and-algorithms",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Data Structures and Algorithms",
|
||||
"url": "https://www.javatpoint.com/data-structure-tutorial",
|
||||
@@ -655,11 +645,6 @@
|
||||
"title": "Complete Binary Tree - Programiz",
|
||||
"url": "https://www.programiz.com/dsa/complete-binary-tree",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Complete Binary Trees",
|
||||
"url": "https://www.wscubetech.com/resources/dsa/complete-binary-tree",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -786,14 +771,8 @@
|
||||
},
|
||||
"HZ1kk0TQ13FLC9t13BZl5": {
|
||||
"title": "Adjacency Matrix",
|
||||
"description": "An adjacency matrix is a square matrix used to represent a finite graph. It is used to represent the connections between vertices in a graph. The matrix is filled with 0s and 1s, where a 1 represents a connection between two vertices and a 0 represents no connection.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Adjacency Matrix",
|
||||
"url": "https://en.wikipedia.org/wiki/Adjacency_matrix",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"rTnKJcPniUtqvfOyC88N0": {
|
||||
"title": "Adjacency List",
|
||||
@@ -1038,14 +1017,8 @@
|
||||
},
|
||||
"7a6-AnBI-3tAU1dkOvPkx": {
|
||||
"title": "Common Algorithms",
|
||||
"description": "Here are some common algorithms that you should know. You can find more information about them in the Algorithms section of the Computer Science course.\n\n* Sorting\n* Recursion\n* Searching\n* Cache Algorithms\n* Tree Algorithms\n* Graph Algorithms\n* Greedy Algorithms\n* Backtracking\n* Substring Search\n* Suffix Arrays\n* Dynamic Programming\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Top Algorithms and Data Structures",
|
||||
"url": "https://towardsdatascience.com/top-algorithms-and-data-structures-you-really-need-to-know-ab9a2a91c7b5",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "Here are some common algorithms that you should know. You can find more information about them in the [Algorithms](https://www.khanacademy.org/computing/computer-science/algorithms) section of the Computer Science course.\n\n* Sorting\n* Recursion\n* Searching\n* Cache Algorithms\n* Tree Algorithms\n* Graph Algorithms\n* Greedy Algorithms\n* Backtracking\n* Substring Search\n* Suffix Arrays\n* Dynamic Programming",
|
||||
"links": []
|
||||
},
|
||||
"0_qNhprnXU3i8koW3XTdD": {
|
||||
"title": "Tail Recursion",
|
||||
@@ -1170,28 +1143,22 @@
|
||||
"XwyqBK9rgP1MMcJrdIzm5": {
|
||||
"title": "Linear Search",
|
||||
"description": "Linear search is a very simple algorithm that is used to search for a value in an array. It sequentially checks each element of the array until a match is found or until all the elements have been searched.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Linear Search",
|
||||
"url": "https://www.programiz.com/dsa/linear-search",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"4wGBYFZpcdTt97WTbSazx": {
|
||||
"title": "Bubble Sort",
|
||||
"description": "Bubble sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Bubble Sort Algorithm",
|
||||
"url": "https://www.programiz.com/dsa/bubble-sort",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Bubble Sort",
|
||||
"url": "https://www.youtube.com/watch?v=P00xJgWzz2c&index=1&list=PL89B61F78B552C1AB",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "Analyzing Bubble Sort",
|
||||
"url": "https://www.youtube.com/watch?v=ni_zk257Nqo&index=7&list=PL89B61F78B552C1AB",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "Bubble sort in 2 minutes",
|
||||
"url": "https://youtu.be/xli_FI7CuzA",
|
||||
@@ -1408,13 +1375,7 @@
|
||||
"Yf5gOIe7oiL19MjEVcpdw": {
|
||||
"title": "Depth First Search",
|
||||
"description": "Depth first search is a graph traversal algorithm that starts at a root node and explores as far as possible along each branch before backtracking.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Depth-first Search",
|
||||
"url": "https://en.wikipedia.org/wiki/Depth-first_search",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"eY4nK2lPYsrR-a_8y2sao": {
|
||||
"title": "Bellman Ford's Algorithm",
|
||||
@@ -1481,13 +1442,8 @@
|
||||
},
|
||||
"aBjBHpq_OajgQjxdCobXD": {
|
||||
"title": "Finding Hamiltonian Paths",
|
||||
"description": "Hamiltonian paths are paths that visit every node in a graph exactly once. They are named after the famous mathematician Hamilton. Hamiltonian paths are a special case of Hamiltonian cycles, which are cycles that visit every node in a graph exactly once.\n\nVisit the following resources to learn more:",
|
||||
"description": "Hamiltonian paths are paths that visit every node in a graph exactly once. They are named after the famous mathematician [Hamilton](https://en.wikipedia.org/wiki/William_Rowan_Hamilton). Hamiltonian paths are a special case of [Hamiltonian cycles](https://en.wikipedia.org/wiki/Hamiltonian_cycle), which are cycles that visit every node in a graph exactly once.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Hamiltonian Cycles",
|
||||
"url": "https://en.wikipedia.org/wiki/Hamiltonian_cycle",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Hamiltonian Path",
|
||||
"url": "https://www.hackerearth.com/practice/algorithms/graphs/hamiltonian-path/tutorial/",
|
||||
@@ -2011,13 +1967,13 @@
|
||||
"description": "Class Diagrams are used to model the static structure of a system. They are used to show the classes, their attributes, operations (or methods), and the relationships between objects.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Class Diagrams",
|
||||
"url": "https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/",
|
||||
"type": "article"
|
||||
"title": "UML Class Diagram Tutorial",
|
||||
"url": "https://www.youtube.com/watch?v=UI6lqHOVHic",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "UML Class Diagram Tutorial",
|
||||
"url": "https://www.youtube.com/watch?v=UI6lqHOVHic",
|
||||
"url": "https://www.youtube.com/watch?v=3cmzqZzwNDM&list=PLfoY2ARMh0hC2FcJKP5voAKCpk6PZXSd5&index=2",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
@@ -2398,6 +2354,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2449,6 +2425,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2500,6 +2496,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2551,6 +2567,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2607,6 +2643,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2885,6 +2941,11 @@
|
||||
"url": "https://www.youtube.com/watch?v=svfnVhJOfMc&index=8&list=PLA5Lqm4uh9Bbq-E0ZnqTIa8LRaL77ica6",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "MIT 6.851 - Memory Hierarchy Models",
|
||||
"url": "https://www.youtube.com/watch?v=V3omVLzI0WE&index=7&list=PLUl4u3cNGP61hsJNdULdudlRL493b-XZf",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "B-Trees (playlist) in 26 minutes",
|
||||
"url": "https://www.youtube.com/playlist?list=PL9xmBV_5YoZNFPPv98DjTdD9X6UI9KMHz",
|
||||
@@ -2996,11 +3057,6 @@
|
||||
"title": "CDN",
|
||||
"description": "A CDN is a network of servers that are distributed geographically. The servers are connected to each other and to the internet. The servers are used to deliver content to users. The content is delivered to the user from the server that is closest to the user. This is done to reduce latency and improve the performance of the content delivery.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is a CDN?",
|
||||
"url": "https://www.cloudflare.com/learning/cdn/what-is-a-cdn/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Content Delivery Network (CDN) - System Design",
|
||||
"url": "https://dev.to/karanpratapsingh/system-design-the-complete-course-10fo#content-delivery-network-cdn",
|
||||
@@ -3107,11 +3163,6 @@
|
||||
"title": "GraphQL",
|
||||
"description": "GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated GraphQL Roadmap",
|
||||
"url": "https://roadmap.sh/graphql",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Apollo GraphQL Tutorials",
|
||||
"url": "https://www.apollographql.com/tutorials/",
|
||||
@@ -3176,7 +3227,7 @@
|
||||
"description": "Long polling is a technique used to implement server push functionality over HTTP. It is a method of opening a request on the server and keeping it open until an event occurs, at which point the server responds. This is in contrast to a regular HTTP request, where the server responds immediately with whatever data is available at the time.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Long Polling",
|
||||
"title": "Long polling",
|
||||
"url": "https://javascript.info/long-polling",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -3201,18 +3252,7 @@
|
||||
"bVjI14VismTHNCyA0mEBP": {
|
||||
"title": "Web Sockets",
|
||||
"description": "Web sockets are a bidirectional communication protocol between a client and a server. They are used for real-time applications like chat, multiplayer games, and live data updates. Web sockets are also used to establish a connection between a server and a client. This connection is then used to send data in both directions.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "WebSockets",
|
||||
"url": "https://en.wikipedia.org/wiki/WebSocket",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Web Sockets API",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"JckRqZA8C6IqQLPpTCgf4": {
|
||||
"title": "SSE",
|
||||
@@ -3240,12 +3280,12 @@
|
||||
"description": "A database is a collection of useful data of one or more related organizations structured in a way to make data an asset to the organization. A database management system is a software designed to assist in maintaining and extracting large collections of data in a timely fashion.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is a Database?",
|
||||
"title": "Oracle: What is a Database?",
|
||||
"url": "https://www.oracle.com/database/what-is-database/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What are Databases?",
|
||||
"title": "Prisma.io: What are Databases?",
|
||||
"url": "https://www.prisma.io/dataguide/intro/what-are-databases",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -3322,13 +3362,7 @@
|
||||
"ii1vF74u3yrFNlw_21b3B": {
|
||||
"title": "DDL",
|
||||
"description": "DDL or Data Definition Language actually consists of the SQL commands that can be used to define the database schema. It simply deals with descriptions of the database schema and is used to create and modify the structure of database objects in the database. DDL is a set of SQL commands used to create, modify, and delete database structures but not data. These commands are normally not used by a general user, who should be accessing the database via an application.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "DDL",
|
||||
"url": "https://en.wikipedia.org/wiki/Data_definition_language",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"tcQSH-eAvJUZuePTDjAIb": {
|
||||
"title": "DML",
|
||||
@@ -3349,29 +3383,12 @@
|
||||
"05lkb3B86Won7Rkf-8DeD": {
|
||||
"title": "DQL",
|
||||
"description": "DQL statements are used for performing queries on the data within schema objects. The purpose of the DQL Command is to get some schema relation based on the query passed to it. We can define DQL as follows it is a component of SQL statement that allows getting data from the database and imposing order upon it. It includes the SELECT statement. This command allows getting the data out of the database to perform operations with it. When a SELECT is fired against a table or tables the result is compiled into a further temporary table, which is displayed or perhaps received by the program i.e. a front-end.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Data Query Language",
|
||||
"url": "https://en.wikipedia.org/wiki/Data_query_language",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"4bUmfuP2qgcli8I2Vm9zh": {
|
||||
"title": "DCL",
|
||||
"description": "DCL includes commands such as GRANT and REVOKE which mainly deal with the rights, permissions, and other controls of the database system.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "DCL",
|
||||
"url": "https://en.wikipedia.org/wiki/Data_Control_Language",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "DCL Commands",
|
||||
"url": "https://www.geeksforgeeks.org/sql-ddl-dql-dml-dcl-tcl-commands/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"_sm63rZNKoibVndeNgOpW": {
|
||||
"title": "Locking",
|
||||
@@ -3418,13 +3435,7 @@
|
||||
"q3nRhTYS5wg9tYnQe2sCF": {
|
||||
"title": "BASE",
|
||||
"description": "The rise in popularity of NoSQL databases provided a flexible and fluidity with ease to manipulate data and as a result, a new database model was designed, reflecting these properties. The acronym BASE is slightly more confusing than ACID but however, the words behind it suggest ways in which the BASE model is different and acronym BASE stands for:-\n\n* **B**asically **A**vailable\n* **S**oft state\n* **E**ventual consistency\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "BASE Model vs. ACID Model",
|
||||
"url": "https://www.geeksforgeeks.org/acid-model-vs-base-model-for-database/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"uqfeiQ9K--QkGNwks4kjk": {
|
||||
"title": "CAP Theorem",
|
||||
@@ -3793,7 +3804,7 @@
|
||||
"description": "Public-key cryptography, or asymmetric cryptography, is the field of cryptographic systems that use pairs of related keys. Each key pair consists of a public key and a corresponding private key. Key pairs are generated with cryptographic algorithms based on mathematical problems termed one-way functions.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Public-key Cryptography",
|
||||
"title": "Public-key cryptography - Wikipedia",
|
||||
"url": "https://en.wikipedia.org/wiki/Public-key_cryptography",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -3866,12 +3877,7 @@
|
||||
"type": "opensource"
|
||||
},
|
||||
{
|
||||
"title": "OWASP",
|
||||
"url": "https://owasp.org/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "OWASP - Wiki",
|
||||
"title": "Wikipedia - OWASP",
|
||||
"url": "https://en.wikipedia.org/wiki/OWASP",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -3920,7 +3926,7 @@
|
||||
},
|
||||
"1eglba39q426Nh0E0qcdj": {
|
||||
"title": "How CPU Executes Programs",
|
||||
"description": "The CPU executes programs by repeatedly fetching instructions from memory, decoding them to understand the operation, and then executing those operations. This cycle, called the fetch-decode-execute cycle, continues for each instruction in the program, with the CPU using registers for temporary storage and a program counter to keep track of the next instruction. Modern CPUs use techniques like pipelining and caches to speed up this process, enabling them to execute complex programs efficiently.\n\nVisit the following resources to learn more:",
|
||||
"description": "Visit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Explore top posts about Computing",
|
||||
@@ -3928,7 +3934,7 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "How CPU Executes a Program",
|
||||
"title": "How CPU executes a program",
|
||||
"url": "https://www.youtube.com/watch?v=XM4lGflQFvA",
|
||||
"type": "video"
|
||||
}
|
||||
@@ -3958,13 +3964,8 @@
|
||||
},
|
||||
"AxiGqbteK7ZSXEUt_zckH": {
|
||||
"title": "Instructions and Programs",
|
||||
"description": "Instructions are the most basic commands a CPU can understand, directing it to perform specific actions like adding numbers or moving data. A program, on the other hand, is a collection of these instructions, organized in a sequence to accomplish a particular task. Think of instructions as individual words and a program as a complete sentence or story; the CPU executes these instructions one by one, following the program's logic, to achieve the desired outcome.\n\nVisit the following resources to learn more:",
|
||||
"description": "Visit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Instruction and Programs",
|
||||
"url": "https://nerdfighteria.info/v/zltgXvg6r3k/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Instructions and Programs",
|
||||
"url": "https://youtu.be/zltgXvg6r3k",
|
||||
@@ -3974,13 +3975,8 @@
|
||||
},
|
||||
"DjTQjMbika4_yTzrBpcmB": {
|
||||
"title": "CPU Cache",
|
||||
"description": "A CPU cache is a hardware cache used by the central processing unit of a computer to reduce the average cost to access data from the main memory. A cache is a smaller, faster memory, located closer to a processor core, which stores copies of the data from frequently used main memory locations.\n\nVisit the following resources to learn more:",
|
||||
"description": "Visit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "What is CPU Cache",
|
||||
"url": "https://www.howtogeek.com/854138/what-is-cpu-cache/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Explore top posts about Computing",
|
||||
"url": "https://app.daily.dev/tags/computing?ref=roadmapsh",
|
||||
@@ -4074,10 +4070,10 @@
|
||||
},
|
||||
"xUo5Ox_HTgGyeQMDIkVyK": {
|
||||
"title": "Concurrency in Multiple Cores",
|
||||
"description": "Concurrency or Parallelism is simultaneous execution of processes on a multiple cores per CPU or multiple CPUs (on a single motherboard). Concurrency is when Parallelism is achieved on a single core/CPU by using scheduling algorithms that divides the CPU's time (time-slice).\n\nVisit the following resources to learn more:",
|
||||
"description": "Visit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Difference between Multi-core and concurrent Programming?",
|
||||
"title": "What is the difference between multicore and concurrent programming?",
|
||||
"url": "https://stackoverflow.com/questions/5372861/what-is-the-difference-between-multicore-and-concurrent-programming",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -4085,17 +4081,12 @@
|
||||
"title": "Concurrency in Multicore systems",
|
||||
"url": "https://cs.stackexchange.com/questions/140793/concurrency-in-multiple-core",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Mastering Concurrency",
|
||||
"url": "https://www.harrisonclarke.com/blog/mastering-concurrency-a-guide-for-software-engineers",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Ge2nagN86ofa2y-yYR1lv": {
|
||||
"title": "Scheduling Algorithms",
|
||||
"description": "CPU Scheduling is the process of selecting a process from the ready queue and allocating the CPU to it. The selection of a process is based on a particular scheduling algorithm. The scheduling algorithm is chosen depending on the type of system and the requirements of the processes.\n\nHere is the list of some of the most commonly used scheduling algorithms:\n\n* **First Come First Serve (FCFS):** The process that arrives first is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Job First (SJF):** The process with the smallest execution time is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Remaining Time First (SRTF):** The process with the smallest remaining execution time is allocated the CPU first. It is a preemptive algorithm.\n* **Round Robin (RR):** The process is allocated the CPU for a fixed time slice. The time slice is usually 10 milliseconds. It is a preemptive algorithm.\n* **Priority Scheduling:** The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Feedback Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. If a process is preempted, it is moved to the next queue. It is a preemptive algorithm.\n* **Highest Response Ratio Next(HRRN):** CPU is allotted to the next process which has the highest response ratio and not to the process having less burst time. It is a Non-Preemptive algorithm.\n* **Lottery Scheduling:** The process is allocated the CPU based on a lottery system. It is a preemptive algorithm.\n\nVisit the following resources to learn more",
|
||||
"description": "CPU Scheduling is the process of selecting a process from the ready queue and allocating the CPU to it. The selection of a process is based on a particular scheduling algorithm. The scheduling algorithm is chosen depending on the type of system and the requirements of the processes.\n\nHere is the list of some of the most commonly used scheduling algorithms:\n\n* **First Come First Serve (FCFS):** The process that arrives first is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Job First (SJF):** The process with the smallest execution time is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Remaining Time First (SRTF):** The process with the smallest remaining execution time is allocated the CPU first. It is a preemptive algorithm.\n* **Round Robin (RR):** The process is allocated the CPU for a fixed time slice. The time slice is usually 10 milliseconds. It is a preemptive algorithm.\n* **Priority Scheduling:** The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Feedback Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. If a process is preempted, it is moved to the next queue. It is a preemptive algorithm.\n* **Highest Response Ratio Next(HRRN):** CPU is allotted to the next process which has the highest response ratio and not to the process having less burst time. It is a Non-Preemptive algorithm.\n* **Lottery Scheduling:** The process is allocated the CPU based on a lottery system. It is a preemptive algorithm.\n\nVisit the following resources to learn more :",
|
||||
"links": [
|
||||
{
|
||||
"title": "CPU Scheduling in Operating Systems - geeksforgeeks",
|
||||
@@ -4129,7 +4120,7 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Interrupts",
|
||||
"title": "Video on Interrupts",
|
||||
"url": "https://youtu.be/iKlAWIKEyuw",
|
||||
"type": "video"
|
||||
}
|
||||
@@ -4231,6 +4222,26 @@
|
||||
"title": "NP Completeness IV",
|
||||
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
|
||||
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
|
||||
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
|
||||
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"title": "Introduction to Data Analytics",
|
||||
"url": "https://www.coursera.org/learn/introduction-to-data-analytics",
|
||||
"type": "course"
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -265,7 +265,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Replace Function",
|
||||
"url": "https://support.microsoft.com/en-us/office/replace-function-8d799074-2425-4a8a-84bc-82472868878a",
|
||||
"url": "https://support.microsoft.com/en-us/office/replace-function-6acf209b-01b7-4078-b4b8-e0a4ef67d181",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -670,7 +670,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Outliers",
|
||||
"url": "https://www.mathsisfun.com/data/outliers.html",
|
||||
"url": "%5Bhttps://www.mathsisfun.com/data/outliers.html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3027,13 +3027,13 @@
|
||||
"description": "Originally created at Lyft, Envoy is a high-performance data plane designed for service mesh architectures. Lyft open sourced it and donated it to the CNCF, where it is now one of the CNCF’s graduated open source projects. Envoy is a self contained process that is designed to run alongside every application server. All of the Envoys form a transparent communication mesh in which each application sends and receives messages to and from localhost and is unaware of the network topology.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "envoyproxy/envoy",
|
||||
"url": "https://github.com/envoyproxy/envoy",
|
||||
"type": "opensource"
|
||||
"title": "Envoy Website",
|
||||
"url": "https://www.envoyproxy.io/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Envoy",
|
||||
"url": "https://www.envoyproxy.io/",
|
||||
"title": "envoyproxy/envoy",
|
||||
"url": "https://github.com/envoyproxy/envoy",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -162,14 +162,8 @@
|
||||
},
|
||||
"7PBmYoSmIgZT21a2Ip3_S": {
|
||||
"title": "Trust / Influence Building",
|
||||
"description": "Building trust and influence is crucial for any Engineering Manager. This involves establishing a solid reputation, delivering on promises and being an active listener to your team's ideas and issues. It's a manager's job to ensure there's an open, honest environment that promotes trust. Balancing delegation and taking charge, especially in difficult situations, is key to building influence.\n\nOne challenge in this area is building trust between team members of varying experiences and skills. Managers must not only show the team they're competent, but also that they value everyone's inputs. They can achieve this by promoting inclusivity and praising team contributions regularly.\n\nBeing patient, communicate clearly, and showing empathy are critical skills that can help an Engineering Manager in trust and influence building. By embodying these traits, managers can build a stronger, united, and more effective engineering team.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Understanding The Trust Equation",
|
||||
"url": "https://trustedadvisor.com/why-trust-matters/understanding-trust/understanding-the-trust-equation",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "Building trust and influence is crucial for any Engineering Manager. This involves establishing a solid reputation, delivering on promises and being an active listener to your team's ideas and issues. It's a manager's job to ensure there's an open, honest environment that promotes trust. Balancing delegation and taking charge, especially in difficult situations, is key to building influence.\n\nOne challenge in this area is building trust between team members of varying experiences and skills. Managers must not only show the team they're competent, but also that they value everyone's inputs. They can achieve this by promoting inclusivity and praising team contributions regularly.\n\nBeing patient, communicate clearly, and showing empathy are critical skills that can help an Engineering Manager in trust and influence building. By embodying these traits, managers can build a stronger, united, and more effective engineering team.",
|
||||
"links": []
|
||||
},
|
||||
"b3qoH_LuW-Gz4N8WdGnZs": {
|
||||
"title": "One-on-One Meetings",
|
||||
|
||||
@@ -364,13 +364,8 @@
|
||||
"description": "Joints in game development primarily refer to the connections between two objects, often used in the context of physics simulations and character animations. These might simulate the physics of real-world joints like hinges or springs. Developers can control various characteristics of joints such as their constraints, forces, and reactions. The different types come with various properties suitable for specific needs. For example, Fixed joints keep objects together, Hinge joints allow rotation around an axis, and Spring joints apply a force to keep objects apart.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Introduction to joints",
|
||||
"url": "https://docs.unity3d.com/Manual/Joints.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Character Rigging for Video Games",
|
||||
"url": "https://game-ace.com/blog/character-rigging-for-video-games/",
|
||||
"title": "Game Character Rigging Fundamentals",
|
||||
"url": "https://learn.unity.com/project/game-character-rigging-fundamentals",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -604,16 +599,16 @@
|
||||
},
|
||||
"vWLKYK2KUzV1fO-vQunzW": {
|
||||
"title": "EPA",
|
||||
"description": "The **EPA** (Expanding Polytope Algorithm) is an iterative algorithm used for calculating the penetration depth between two shapes in collision detection. It is commonly used in physics engines and robotics. The algorithm takes the resulting simplex from a previously applied GJK algorithm, iteratively expanding the polytope towards the Minkowski Difference boundary until it finds the closest point to the origin. The vector from that point to the origin is the penetration vector and its magnitude is equal to the penetration depth between the two shapes.\n\nVisit the following resources to learn more:",
|
||||
"description": "The **EPA**, also known as the _Environmental Protection Agency_, is not typically related to game development or the concept of intersection within this context. However, in game development, EPA might refer to an 'Event-driven Process chain Architecture' or some other game-specific acronym. In this domain, different terminologies and acronyms are often used to express complex architectures, designs, or functionalities. If you have encountered EPA in a game development context, it might be best to refer to the specific documentation or guide where it was described for a better understanding. Understanding the context is key to untangle the meaning of such abbreviations.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "EPA: Collision response algorithm for 2D/3D - winter.dev",
|
||||
"url": "https://winter.dev/articles/epa-algorithm",
|
||||
"title": "Environmental Sustainability in Game Development",
|
||||
"url": "https://polydin.com/environmental-sustainability-in-game-development/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "EPA (Expanding Polytope Algorithm) - dyn4j",
|
||||
"url": "https://dyn4j.org/2010/05/epa-expanding-polytope-algorithm/",
|
||||
"title": "Gaming Sustainability - Microsoft Game Dev",
|
||||
"url": "https://learn.microsoft.com/en-us/gaming/sustainability/sustainability-overview",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
@@ -749,7 +744,7 @@
|
||||
},
|
||||
"7OffO2mBmfBKqPBTZ9ngI": {
|
||||
"title": "Godot",
|
||||
"description": "Godot is an open-source, multi-platform game engine that is known for being feature-rich and user-friendly. It is developed by hundreds of contributors from around the world and supports the creation of both 2D and 3D games. Godot uses its own scripting language, GDScript, which is similar to Python, but it also supports C#. It is equipped with a unique scene system and comes with a multitude of tools that can expedite the development process. Godot's design philosophy centers around flexibility, extensibility, and ease of use, providing a handy tool for both beginners and pros in game development.\n\nVisit the following resources to learn more:",
|
||||
"description": "Godot is an open-source, multi-platform game engine that is known for being feature-rich and user-friendly. It is developed by hundreds of contributors from around the world and supports the creation of both 2D and 3D games. Godot uses its own scripting language, GDScript, which is similar to Python, but it also supports C# and visual scripting. It is equipped with a unique scene system and comes with a multitude of tools that can expedite the development process. Godot's design philosophy centers around flexibility, extensibility, and ease of use, providing a handy tool for both beginners and pros in game development.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "godotengine/godot",
|
||||
@@ -768,17 +763,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Godot in 100 Seconds",
|
||||
"url": "https://www.youtube.com/watch?v=QKgTZWbwD1U",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "Tutorial - How to make a Video Game in Godot",
|
||||
"url": "https://www.youtube.com/watch?v=LOhfqjmasi0",
|
||||
"type": "video"
|
||||
},
|
||||
{
|
||||
"title": "Tutorial - How to make 3D Games in Godot",
|
||||
"url": "https://www.youtube.com/watch?v=ke5KpqcoiIU",
|
||||
"url": "https://m.youtube.com/watch?v=QKgTZWbwD1U",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
@@ -897,8 +882,8 @@
|
||||
"description": "**C** and **C++ (commonly known as CPP)** are two of the most foundational high-level programming languages in computer science. **C** was developed in the 1970s and it is a procedural language, meaning it follows a step-by-step approach. Its fundamental principles include structured programming and lexical variable scope.\n\nOn the other hand, **C++** follows the paradigm of both procedural and object-oriented programming. It was developed as an extension to C to add the concept of \"classes\" - a core feature of object-oriented programming. C++ enhances C by introducing new features like function overloading, exception handling, and templates.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "C Programming Language",
|
||||
"url": "https://en.wikipedia.org/wiki/C_%28programming_language%29",
|
||||
"title": "The C Programming Language",
|
||||
"url": "https://www.iso.org/standard/74528.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1440,7 +1440,7 @@
|
||||
},
|
||||
"uxqJzQFRcALqatNRIWR0w": {
|
||||
"title": "Unstaged Changes",
|
||||
"description": "For changes that are not yet staged with `git add`, such as untracked new files or modified existing ones , use `git diff`. This command compares your working directory (your current changes) against the staging area (changes already staged with `git add`). It’s a useful tool for reviewing local modifications before deciding whether to stage them for future commits.\n\nThe `--unified` option (or -U) controls the number of context lines shown in the diff output. By default, Git shows 3 lines of context around each change. For example, `git diff --unified=5` will display 5 lines of context around each change, making it easier to understand the surrounding code or content.",
|
||||
"description": "For changes that are not yet staged with `git add`, such as untracked new files or modified existing ones, use `git diff --unified`. This command compares your working directory against the latest committed version of each file. It's a useful tool for reviewing any local modifications before deciding whether to stage them for future commits.",
|
||||
"links": [
|
||||
{
|
||||
"title": "What are unstaged changes in GitHub?",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1023,16 +1023,16 @@
|
||||
},
|
||||
"XteNExIZN3_g95_dPCopY": {
|
||||
"title": "Exitting / Exit Codes",
|
||||
"description": "Exiting is a way of terminating a Node.js process by using node.js process module.\n\nVisit the following resources to learn more:",
|
||||
"description": "`Exiting` is a way of terminating a Node.js process by using node.js process module.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Exit Documentation",
|
||||
"url": "https://nodejs.org/api/process.html#event-exit",
|
||||
"title": "Node.js Docs on exit",
|
||||
"url": "https://nodejs.org/docs/latest/api/process.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "How to Exit a Process in Node.js",
|
||||
"url": "https://betterstack.com/community/questions/how-to-exit-in-node-js/",
|
||||
"url": "https://www.knowledgehut.com/blog/web-development/node-js-process-exit",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -319,13 +319,8 @@
|
||||
"description": "The Null Safe Operator is a handy feature in PHP which deals with an issue that often pops up when working with objects: trying to access properties or methods on an object that might be null. Instead of a fatal error, the PHP Null Safe Operator (indicated by ?->) allows null values to be returned safely, making your code more robust. Here's a quick example, consider $session?->user?->name. If $session or user is null, PHP will stop further execution and simply return null. This makes PHP more resilient when processing unpredictable data.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "The Basics - Manual",
|
||||
"url": "https://www.php.net/manual/en/language.oop5.basic.php",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "PHP RFC: Nullsafe operator",
|
||||
"url": "https://wiki.php.net/rfc/nullsafe_operator",
|
||||
"title": "Null Safe Operator",
|
||||
"url": "https://www.php.net/manual/en/language.oop5.nullsafe.php",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
@@ -398,7 +393,7 @@
|
||||
},
|
||||
"RkNjYva8o_jXp9suz5YdG": {
|
||||
"title": "Named Arguments",
|
||||
"description": "Named arguments in PHP, introduced with PHP 8.0, allow you to specify the values of required parameters by their names, instead of their position in the function call, thus making your code more readable, reducing mistakes, and allowing for unimportant arguments to be skipped. Here's an array\\_fill() function using named arguments:\n\n <?php\n $a = array_fill(start_index: 0, count: 100, value: 50);\n \n\nIn this code snippet, the parameters are passed by their names ('start\\_index', 'count', 'value'), not by their order in the function definition.\n\nVisit the following resources to learn more:",
|
||||
"description": "Named arguments in PHP, introduced with PHP 8.0, allow you to specify the values of required parameters by their names, instead of their position in the function call, thus making your code more readable, reducing mistakes, and allowing for unimportant arguments to be skipped. Here's an array\\_fill() function using named arguments:\n\n <?php\n $a = array_fill(start_index: 0, num: 100, value: 50);\n \n\nIn this code snippet, the parameters are passed by their names ('start\\_index', 'num', 'value'), not by their order in the function definition.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Named Arguments",
|
||||
@@ -1020,12 +1015,12 @@
|
||||
"description": "Symfony is a set of PHP components and a framework for web projects. It aims to speed up the creation and maintenance of web applications and replace the recurring coding tasks. Symfony uses Composer, a PHP dependency manager, to manage its components. Below is an example of creating a new Symfony project:\n\n composer create-project symfony/website-skeleton myproject\n \n\nThis will download and install a new Symfony project in the 'myproject' directory. Symfony's components are reusable PHP libraries that will help you complete tasks, like routing, templating, or even creating form handling.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Symfony",
|
||||
"title": "Symphony",
|
||||
"url": "https://symfony.com/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Symfony Documentation",
|
||||
"title": "Symphony Documentation",
|
||||
"url": "https://symfony.com/doc/current/index.html",
|
||||
"type": "article"
|
||||
}
|
||||
|
||||
@@ -553,11 +553,6 @@
|
||||
"title": "Modules in Python",
|
||||
"url": "https://www.programiz.com/python-programming/modules",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Python Modules and Packages",
|
||||
"url": "https://realpython.com/python-modules-packages/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -652,7 +647,7 @@
|
||||
},
|
||||
"P_Di-XPSDITmU3xKQew8G": {
|
||||
"title": "Object Oriented Programming",
|
||||
"description": "In Python, object-oriented Programming (OOPs) is a programming paradigm that uses objects and classes in programming. It aims to implement real-world entities like inheritance, polymorphism, encapsulation, etc., in programming. The main concept of OOPs is to bind the data and the functions that work on that together as a single unit so that no other part of the code can access this data.\n\nVisit the following resources to learn more:",
|
||||
"description": "In Python, object-oriented Programming (OOPs) is a programming paradigm that uses objects and classes in programming. It aims to implement real-world entities like inheritance, polymorphisms, encapsulation, etc. in the programming. The main concept of OOPs is to bind the data and the functions that work on that together as a single unit so that no other part of the code can access this data.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Object Oriented Programming in Python",
|
||||
@@ -715,7 +710,7 @@
|
||||
},
|
||||
"zAS4YiEJ6VPsyABrkIG8i": {
|
||||
"title": "Methods, Dunder",
|
||||
"description": "A method in python is somewhat similar to a function, except it is associated with object/classes. Methods in python are very similar to functions except for two major differences.\n\n* The method is implicitly used for an object for which it is called.\n* The method is accessible to data that is contained within the class.\n\nDunder or magic methods in Python are the methods that have two prefix and suffix underscores in the method name. Dunder here means “Double Under (Underscores)”. These are commonly used for operator overloading. Few examples for magic methods are: **`__init__`**, **`__add__`**, **`__len__`**, **`__repr__`** etc.\n\nVisit the following resources to learn more:",
|
||||
"description": "A method in python is somewhat similar to a function, except it is associated with object/classes. Methods in python are very similar to functions except for two major differences.\n\n* The method is implicitly used for an object for which it is called.\n* The method is accessible to data that is contained within the class.\n\nDunder or magic methods in Python are the methods having two prefix and suffix underscores in the method name. Dunder here means “Double Under (Underscores)”. These are commonly used for operator overloading. Few examples for magic methods are: **`__init__`**, **`__add__`**, **`__len__`**, **`__repr__`** etc.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Method vs Function in Python",
|
||||
@@ -1314,7 +1309,7 @@
|
||||
"description": "An extremely fast Python linter and code formatter, written in Rust.",
|
||||
"links": [
|
||||
{
|
||||
"title": "Ruff documentation",
|
||||
"title": "ruff documentation",
|
||||
"url": "https://docs.astral.sh/ruff/",
|
||||
"type": "article"
|
||||
}
|
||||
|
||||
@@ -534,7 +534,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "RPOP Documentation",
|
||||
"url": "https://redis.io/docs/latest/commands/rpop/",
|
||||
"url": "https://redis.io/docs/latest/commands/rpush/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
@@ -954,19 +954,8 @@
|
||||
},
|
||||
"jrgaoDnt_RxTu79hk4hCD": {
|
||||
"title": "Atomicity in Redis",
|
||||
"description": "Atomicity in Redis refers to the property that ensures a set of operations is executed as a single, indivisible unit. This means that either all the operations are executed successfully or none of them are. Atomicity is crucial in Redis to maintain consistency, especially when multiple operations need to be performed together.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Atomicity with Lua",
|
||||
"url": "https://redis.io/learn/develop/java/spring/rate-limiting/fixed-window/reactive-lua",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Atomicity in Redis operations",
|
||||
"url": "https://lucaspin.medium.com/atomicity-in-redis-operations-a1d7bc9f4a90",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "Atomicity in Redis refers to the property that ensures a set of operations is executed as a single, indivisible unit. This means that either all the operations are executed successfully or none of them are. Atomicity is crucial in Redis to maintain consistency, especially when multiple operations need to be performed together.\n\nLearn more from the following resources:\n\n* [@official@Atomicity with Lua](https://redis.io/learn/develop/java/spring/rate-limiting/fixed-window/reactive-lua) -[@article@Atomicity in Redis operations](https://lucaspin.medium.com/atomicity-in-redis-operations-a1d7bc9f4a90)",
|
||||
"links": []
|
||||
},
|
||||
"LHlwjN3WHYUBUafzzwsWQ": {
|
||||
"title": "Pipelining",
|
||||
|
||||
@@ -257,18 +257,7 @@
|
||||
"E4H3hniIW6hKpH3Qr--N5": {
|
||||
"title": "C/C++",
|
||||
"description": "\"C\" and \"C++\", often written as \"C/CPP\", are two significantly prominent and similar programming languages widely used in server-side game development. \"C\" is a procedural language, which means that it follows a step-by-step procedure to solve a problem, while \"C++\" is both a procedural and object-oriented programming (OOP) language. This dual nature of \"C++\" allows it to handle more complex interrelated data and functions efficiently, which is a beneficial feature in game development. Moreover, \"C++\" is an extension of \"C\", meaning that any legal \"C\" program is also a valid \"C++\" program. Both languages offer a high degree of control over system resources and memory, making them an excellent choice for building fast and efficient server-side applications, such as multiplayer game servers.",
|
||||
"links": [
|
||||
{
|
||||
"title": "C Programming Language",
|
||||
"url": "https://en.wikipedia.org/wiki/C_%28programming_language%29",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "C++ Programming Language",
|
||||
"url": "https://en.wikipedia.org/wiki/C%2B%2B",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"links": []
|
||||
},
|
||||
"DuyUc9a-47Uz03yr4aeyg": {
|
||||
"title": "C#",
|
||||
|
||||
@@ -1326,10 +1326,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"queu-based-load-leveling@LncTxPg-wx8loy55r5NmV.md": {
|
||||
"LncTxPg-wx8loy55r5NmV": {
|
||||
"title": "Queu-based Load Leveling",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "Use a queue that acts as a buffer between a task and a service it invokes in order to smooth intermittent heavy loads that can cause the service to fail or the task to time out. This can help to minimize the impact of peaks in demand on availability and responsiveness for both the task and the service.\n\nLearn more from the following links:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Queue-Based Load Leveling pattern",
|
||||
"url": "https://learn.microsoft.com/en-us/azure/architecture/patterns/queue-based-load-leveling",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2ryzJhRDTo98gGgn9mAxR": {
|
||||
"title": "Publisher/Subscriber",
|
||||
@@ -1659,21 +1665,10 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"n4It-lr7FFtSY83DcGydX": {
|
||||
"backends-for-frontend@n4It-lr7FFtSY83DcGydX.md": {
|
||||
"title": "Backends for Frontend",
|
||||
"description": "Create separate backend services to be consumed by specific frontend applications or interfaces. This pattern is useful when you want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.\n\nTo learn more, visit the following links:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Backends for Frontends pattern",
|
||||
"url": "https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Explore top posts about Frontend Development",
|
||||
"url": "https://app.daily.dev/tags/frontend?ref=roadmapsh",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"4hi7LvjLcv8eR6m-uk8XQ": {
|
||||
"title": "Anti-Corruption Layer",
|
||||
|
||||
@@ -800,6 +800,11 @@
|
||||
"title": "TypeScript Utility Types Guide",
|
||||
"url": "https://camchenry.com/blog/typescript-utility-types",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "TypeScript Utility Types: Key Concepts And Best Practices",
|
||||
"url": "https://marketsplash.com/tutorials/typescript/typescript-utility-types/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 506 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 512 KiB After Width: | Height: | Size: 225 KiB |
@@ -43,7 +43,6 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [AI and Data Scientist Roadmap](https://roadmap.sh/ai-data-scientist)
|
||||
- [AI Engineer Roadmap](https://roadmap.sh/ai-engineer)
|
||||
- [AWS Roadmap](https://roadmap.sh/aws)
|
||||
- [Cloudflare Roadmap](https://roadmap.sh/cloudflare)
|
||||
- [Linux Roadmap](https://roadmap.sh/linux)
|
||||
- [Terraform Roadmap](https://roadmap.sh/terraform)
|
||||
- [Data Analyst Roadmap](https://roadmap.sh/data-analyst)
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { Edge, Node } from 'reactflow';
|
||||
import matter from 'gray-matter';
|
||||
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
|
||||
import { slugify } from '../src/lib/slugger';
|
||||
import { runPromisesInBatchSequentially } from '../src/lib/promise';
|
||||
|
||||
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
||||
import { generateText } from 'ai';
|
||||
|
||||
// ERROR: `__dirname` is not defined in ES module scope
|
||||
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Usage: tsx ./scripts/editor-roadmap-content.ts <roadmapId>
|
||||
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
||||
console.log('GEMINI_API_KEY:', GEMINI_API_KEY);
|
||||
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
|
||||
const roadmapId = process.argv[2];
|
||||
|
||||
const google = createGoogleGenerativeAI({
|
||||
apiKey: process.env.GEMINI_API_KEY,
|
||||
});
|
||||
|
||||
const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
|
||||
if (!roadmapId) {
|
||||
console.error('Roadmap Id is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!allowedRoadmapIds.includes(roadmapId)) {
|
||||
console.error(`Invalid roadmap key ${roadmapId}`);
|
||||
console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const roadmapFrontmatterDir = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapId,
|
||||
`${roadmapId}.md`,
|
||||
);
|
||||
const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
|
||||
const { data } = matter(roadmapFrontmatterRaw);
|
||||
|
||||
const roadmapFrontmatter = data as RoadmapFrontmatter;
|
||||
if (!roadmapFrontmatter) {
|
||||
console.error('Invalid roadmap frontmatter');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (roadmapFrontmatter.renderer !== 'editor') {
|
||||
console.error('Only Editor Rendered Roadmaps are allowed');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const roadmapDir = path.join(
|
||||
ROADMAP_CONTENT_DIR,
|
||||
roadmapId,
|
||||
`${roadmapId}.json`,
|
||||
);
|
||||
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
|
||||
let { nodes, edges } = JSON.parse(roadmapContent) as {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
};
|
||||
const enrichedNodes = nodes
|
||||
.filter(
|
||||
(node) =>
|
||||
node?.type &&
|
||||
['topic', 'subtopic'].includes(node.type) &&
|
||||
node.data?.label,
|
||||
)
|
||||
.map((node) => {
|
||||
// Because we only need the parent id and title for subtopics
|
||||
if (node.type !== 'subtopic') {
|
||||
return node;
|
||||
}
|
||||
|
||||
const parentNodeId =
|
||||
edges.find((edge) => edge.target === node.id)?.source || '';
|
||||
const parentNode = nodes.find((n) => n.id === parentNodeId);
|
||||
|
||||
return {
|
||||
...node,
|
||||
parentId: parentNodeId,
|
||||
parentTitle: parentNode?.data?.label || '',
|
||||
};
|
||||
}) as (Node & { parentId?: string; parentTitle?: string })[];
|
||||
|
||||
const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
|
||||
const stats = await fs.stat(roadmapContentDir).catch(() => null);
|
||||
if (!stats || !stats.isDirectory()) {
|
||||
await fs.mkdir(roadmapContentDir, { recursive: true });
|
||||
}
|
||||
|
||||
function writeTopicContent(
|
||||
roadmapTitle: string,
|
||||
childTopic: string,
|
||||
parentTopic?: string,
|
||||
) {
|
||||
const updatedTitle = roadmapTitle.replace('Roadmap', '').trim().replace('Developer', '');
|
||||
let prompt = `I will give you a topic and you need to write a brief introduction for that in "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:
|
||||
|
||||
# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" or "Subtopic under XYZ" etc.)
|
||||
|
||||
(Briefly explain the topic in one paragraph using simple english. Don't start with explaining how important the topic is with regard to "${roadmapTitle}". Don't say something along the lines of "XYZ plays a crucial role in ${roadmapTitle}". Don't include anything saying "In the context of ${roadmapTitle}". Instead, start with a simple explanation of the topic itself. For example, if the topic is "React", you can start with "React is a JavaScript library for building user interfaces."".)
|
||||
`;
|
||||
|
||||
if (!parentTopic) {
|
||||
prompt += `First topic is: ${childTopic}`;
|
||||
} else {
|
||||
prompt += `First topic is: "${parentTopic} > ${childTopic}"`;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
generateText({
|
||||
model: google('gemini-2.0-flash'),
|
||||
prompt: prompt,
|
||||
providerOptions: {
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
const article = response.text;
|
||||
|
||||
resolve(article);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function writeNodeContent(node: Node & { parentTitle?: string }) {
|
||||
const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`;
|
||||
if (!roadmapContentFiles.includes(nodeDirPattern)) {
|
||||
console.log(`Missing file for: ${nodeDirPattern}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeDir = path.join(roadmapContentDir, nodeDirPattern);
|
||||
const nodeContent = await fs.readFile(nodeDir, 'utf-8');
|
||||
const isFileEmpty = !nodeContent.replace(`# ${node.data.label}`, '').trim();
|
||||
if (!isFileEmpty) {
|
||||
console.log(`❌ Ignoring ${nodeDirPattern}. Not empty.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const topic = node.data.label;
|
||||
const parentTopic = node.parentTitle;
|
||||
|
||||
console.log(`⏳ Generating content for ${topic}...`);
|
||||
let newContentFile = '';
|
||||
if (GEMINI_API_KEY) {
|
||||
newContentFile = (await writeTopicContent(
|
||||
roadmapFrontmatter.title,
|
||||
topic,
|
||||
parentTopic,
|
||||
)) as string;
|
||||
} else {
|
||||
newContentFile = `# ${topic}`;
|
||||
}
|
||||
|
||||
await fs.writeFile(nodeDir, newContentFile, 'utf-8');
|
||||
console.log(`✅ Content generated for ${topic}`);
|
||||
}
|
||||
|
||||
let roadmapContentFiles = await fs.readdir(roadmapContentDir, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
if (!GEMINI_API_KEY) {
|
||||
console.log('----------------------------------------');
|
||||
console.log('GEMINI_API_KEY not found. Skipping gemini api calls...');
|
||||
console.log('----------------------------------------');
|
||||
}
|
||||
const promises = enrichedNodes.map((node) => () => writeNodeContent(node));
|
||||
await runPromisesInBatchSequentially(promises, 20);
|
||||
console.log('✅ All content generated');
|
||||
@@ -153,7 +153,7 @@ const sidebarLinks = [
|
||||
: 'border-r-transparent text-gray-500 hover:border-r-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span class='flex flex-grow items-center'>
|
||||
<span class='flex grow items-center'>
|
||||
{sidebarLink.icon.component ? (
|
||||
<sidebarLink.icon.component
|
||||
className={`${sidebarLink.icon.classes} mr-2`}
|
||||
|
||||
@@ -83,10 +83,10 @@ export function AccountStreak(props: AccountStreakProps) {
|
||||
const totalCircles = leftCircleCount + currentCircleCount + remainingCount;
|
||||
|
||||
return (
|
||||
<div className="relative z-[90] animate-fade-in">
|
||||
<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',
|
||||
'flex items-center justify-center rounded-lg p-1.5 px-2 text-purple-400 hover:bg-purple-100/10 focus:outline-hidden',
|
||||
{
|
||||
'bg-purple-100/10': showDropdown,
|
||||
},
|
||||
|
||||
@@ -128,20 +128,20 @@ export function AccountStreakHeatmap(props: AccountStreakHeatmapProps) {
|
||||
]}
|
||||
classForValue={(value) => {
|
||||
if (!value) {
|
||||
return 'fill-slate-700 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-700 rounded-md [rx:2px] focus:outline-hidden';
|
||||
}
|
||||
|
||||
const { count } = value;
|
||||
if (count >= 20) {
|
||||
return 'fill-slate-200 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-200 rounded-md [rx:2px] focus:outline-hidden';
|
||||
} else if (count >= 10) {
|
||||
return 'fill-slate-300 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-300 rounded-md [rx:2px] focus:outline-hidden';
|
||||
} else if (count >= 5) {
|
||||
return 'fill-slate-400 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-400 rounded-md [rx:2px] focus:outline-hidden';
|
||||
} else if (count >= 3) {
|
||||
return 'fill-slate-500 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-500 rounded-md [rx:2px] focus:outline-hidden';
|
||||
} else {
|
||||
return 'fill-slate-600 rounded-md [rx:2px] focus:outline-none';
|
||||
return 'fill-slate-600 rounded-md [rx:2px] focus:outline-hidden';
|
||||
}
|
||||
}}
|
||||
tooltipDataAttrs={(value: any) => {
|
||||
@@ -159,7 +159,7 @@ export function AccountStreakHeatmap(props: AccountStreakHeatmapProps) {
|
||||
|
||||
<ReactTooltip
|
||||
id="user-activity-tip"
|
||||
className="!rounded-lg !bg-slate-900 !p-1 !px-2 !text-xs"
|
||||
className="rounded-lg! bg-slate-900! p-1! px-2! text-xs!"
|
||||
/>
|
||||
|
||||
<div className="mt-2 flex items-center justify-end">
|
||||
@@ -173,14 +173,14 @@ export function AccountStreakHeatmap(props: AccountStreakHeatmapProps) {
|
||||
data-tooltip-content={`${legend.count} Updates`}
|
||||
>
|
||||
<div
|
||||
className={`h-2.5 w-2.5 ${legend.color} mr-1 rounded-sm`}
|
||||
className={`h-2.5 w-2.5 ${legend.color} mr-1 rounded-xs`}
|
||||
></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"
|
||||
className="rounded-lg! bg-slate-900! p-1! px-2! text-sm!"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export function ActivityTopicsModal(props: ActivityTopicDetailsProps) {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div className={`popup-body relative rounded-lg bg-white p-4 shadow`}>
|
||||
<div className={`popup-body relative rounded-lg bg-white p-4 shadow-sm`}>
|
||||
<span className="mb-2 flex items-center justify-between text-lg font-semibold capitalize">
|
||||
<span className="flex items-center gap-2">
|
||||
{actionType.replace('_', ' ')}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function ProjectProgress(props: ProjectProgressType) {
|
||||
target="_blank"
|
||||
>
|
||||
<ProjectStatus projectStatus={projectStatus} />
|
||||
<span className="ml-2 flex-grow truncate">{projectStatus?.title}</span>
|
||||
<span className="ml-2 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]" />
|
||||
|
||||
@@ -73,7 +73,7 @@ export function ResourceProgress(props: ResourceProgressType) {
|
||||
showActions ? 'pr-7' : '',
|
||||
)}
|
||||
>
|
||||
<span className="flex-grow truncate">{title}</span>
|
||||
<span className="grow truncate">{title}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{parseInt(progressPercentage, 10)}%
|
||||
</span>
|
||||
|
||||
@@ -69,7 +69,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
|
||||
<div
|
||||
ref={popupBodyEl}
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow"
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow-sm"
|
||||
>
|
||||
{isLoading && (
|
||||
<>
|
||||
@@ -99,7 +99,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
<button
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
@@ -110,7 +110,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
setIsLoading(false);
|
||||
}}
|
||||
type="button"
|
||||
className="flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white"
|
||||
className="grow cursor-pointer rounded-lg bg-black py-2 text-center text-white"
|
||||
>
|
||||
+ Add More
|
||||
</button>
|
||||
@@ -126,7 +126,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
<button
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -152,7 +152,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
setSelectedRoadmap(roadmapId);
|
||||
});
|
||||
}}
|
||||
inputClassName="mt-2 mb-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
|
||||
inputClassName="mt-2 mb-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:border-gray-400"
|
||||
placeholder={'Search for roadmap'}
|
||||
/>
|
||||
|
||||
@@ -160,7 +160,7 @@ export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
|
||||
<button
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
@@ -30,7 +30,7 @@ function Input(props: InputProps) {
|
||||
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"
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-xs focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
autoComplete="off"
|
||||
data-1p-ignore=""
|
||||
data-form-type="other"
|
||||
@@ -45,7 +45,7 @@ function Input(props: InputProps) {
|
||||
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"
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 p-2 shadow-xs focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
autoComplete="off"
|
||||
data-1p-ignore=""
|
||||
data-form-type="other"
|
||||
@@ -120,7 +120,7 @@ export function AdvertiseForm() {
|
||||
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">
|
||||
<div className="relative mb-4 rounded-sm border border-red-400 bg-red-100 px-4 py-3 text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -199,7 +199,7 @@ export function AdvertiseForm() {
|
||||
type="checkbox"
|
||||
checked={formData.updates}
|
||||
onChange={handleInputChange}
|
||||
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
className="h-4 w-4 rounded-sm border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 text-sm">
|
||||
@@ -213,7 +213,7 @@ export function AdvertiseForm() {
|
||||
<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"
|
||||
className="flex justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-xs hover:bg-indigo-700 focus:outline-hidden focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<script type='text/javascript'>
|
||||
(function (c, l, a, r, i, t, y) {
|
||||
c[a] =
|
||||
c[a] ||
|
||||
function () {
|
||||
(c[a].q = c[a].q || []).push(arguments);
|
||||
};
|
||||
t = l.createElement(r);
|
||||
t.async = 1;
|
||||
t.src = 'https://www.clarity.ms/tag/' + i;
|
||||
y = l.getElementsByTagName(r)[0];
|
||||
y.parentNode.insertBefore(t, y);
|
||||
})(window, document, 'clarity', 'script', 'qcw723i36o');
|
||||
</script>
|
||||
@@ -6,7 +6,6 @@ declare global {
|
||||
category: string;
|
||||
label?: string;
|
||||
value?: string;
|
||||
callback?: () => void;
|
||||
}) => void;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +17,7 @@ declare global {
|
||||
* @returns void
|
||||
*/
|
||||
window.fireEvent = (props) => {
|
||||
const { action, category, label, value, callback } = props;
|
||||
const { action, category, label, value } = props;
|
||||
if (!window.gtag) {
|
||||
console.warn('Missing GTAG - Analytics disabled');
|
||||
return;
|
||||
@@ -26,16 +25,11 @@ window.fireEvent = (props) => {
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('Analytics event fired', props);
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
|
||||
window.gtag('event', action, {
|
||||
event_category: category,
|
||||
event_label: label,
|
||||
value: value,
|
||||
...(callback ? { event_callback: callback } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
export {};
|
||||
|
||||
@@ -115,7 +115,7 @@ export function CourseLoginPopup(props: CourseLoginPopupProps) {
|
||||
<div className="flex flex-row gap-2">
|
||||
{!isUsingEmail && (
|
||||
<button
|
||||
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
className="grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
onClick={() => setIsUsingEmail(true)}
|
||||
>
|
||||
Use your email address
|
||||
@@ -124,13 +124,13 @@ export function CourseLoginPopup(props: CourseLoginPopupProps) {
|
||||
{isUsingEmail && (
|
||||
<>
|
||||
<button
|
||||
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
className="grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
onClick={() => setEmailNature('login')}
|
||||
>
|
||||
Already have an account
|
||||
</button>
|
||||
<button
|
||||
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
className="grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
|
||||
onClick={() => setEmailNature('signup')}
|
||||
>
|
||||
Create an account
|
||||
|
||||
@@ -38,7 +38,9 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
|
||||
const currentLocation = window.location.href;
|
||||
const url = new URL(currentLocation, window.location.origin);
|
||||
url.searchParams.set(FIRST_LOGIN_PARAM, response?.isNewUser ? '1' : '0');
|
||||
if (response?.isNewUser) {
|
||||
url.searchParams.set(FIRST_LOGIN_PARAM, '1');
|
||||
}
|
||||
window.location.href = url.toString();
|
||||
return;
|
||||
}
|
||||
@@ -70,7 +72,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-xs outline-hidden placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
placeholder="Email Address"
|
||||
value={email}
|
||||
onInput={(e) => setEmail(String((e.target as any).value))}
|
||||
@@ -84,7 +86,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mt-2 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-xs outline-hidden placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onInput={(e) => setPassword(String((e.target as any).value))}
|
||||
@@ -106,7 +108,7 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || isDisabled}
|
||||
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-hidden focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
>
|
||||
{isLoading ? 'Please wait...' : 'Continue'}
|
||||
</button>
|
||||
|
||||
@@ -74,7 +74,7 @@ export function EmailSignupForm(props: EmailSignupFormProps) {
|
||||
min={3}
|
||||
max={50}
|
||||
required
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
placeholder="Full Name"
|
||||
value={name}
|
||||
onInput={(e) => setName(String((e.target as any).value))}
|
||||
@@ -87,7 +87,7 @@ export function EmailSignupForm(props: EmailSignupFormProps) {
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
placeholder="Email Address"
|
||||
value={email}
|
||||
onInput={(e) => setEmail(String((e.target as any).value))}
|
||||
@@ -102,7 +102,7 @@ export function EmailSignupForm(props: EmailSignupFormProps) {
|
||||
min={6}
|
||||
max={50}
|
||||
required
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="block w-full rounded-lg border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onInput={(e) => setPassword(String((e.target as any).value))}
|
||||
@@ -115,7 +115,7 @@ export function EmailSignupForm(props: EmailSignupFormProps) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || isDisabled}
|
||||
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
className="inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-hidden focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
>
|
||||
{isLoading ? 'Please wait...' : 'Continue to Verify Email'}
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,7 @@ export function ForgotPasswordForm() {
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-xs outline-hidden transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
required
|
||||
placeholder="Email Address"
|
||||
value={email}
|
||||
@@ -55,7 +55,7 @@ export function ForgotPasswordForm() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="mt-3 inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
className="mt-3 inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-hidden focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
>
|
||||
{isLoading ? 'Please wait...' : 'Continue'}
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
COURSE_PURCHASE_PARAM,
|
||||
setAuthToken,
|
||||
} from '../../lib/jwt';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import { cn } from '../../../editor/utils/classname.ts';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
|
||||
@@ -81,10 +81,9 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
localStorage.removeItem(GITHUB_LAST_PAGE);
|
||||
setAuthToken(response.token);
|
||||
|
||||
redirectUrl.searchParams.set(
|
||||
FIRST_LOGIN_PARAM,
|
||||
response?.isNewUser ? '1' : '0',
|
||||
);
|
||||
if (response?.isNewUser) {
|
||||
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
|
||||
}
|
||||
|
||||
const shouldTriggerPurchase =
|
||||
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
|
||||
@@ -144,7 +143,7 @@ export function GitHubButton(props: GitHubButtonProps) {
|
||||
<>
|
||||
<button
|
||||
className={cn(
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded-sm border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-hidden hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
className,
|
||||
)}
|
||||
disabled={isLoading || isDisabled}
|
||||
|
||||
@@ -81,10 +81,9 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
redirectUrl = new URL(authRedirectUrl, window.location.origin);
|
||||
}
|
||||
|
||||
redirectUrl.searchParams.set(
|
||||
FIRST_LOGIN_PARAM,
|
||||
response?.isNewUser ? '1' : '0',
|
||||
);
|
||||
if (response?.isNewUser) {
|
||||
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
|
||||
}
|
||||
|
||||
const shouldTriggerPurchase =
|
||||
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
|
||||
@@ -154,7 +153,7 @@ export function GoogleButton(props: GoogleButtonProps) {
|
||||
<>
|
||||
<button
|
||||
className={cn(
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded-sm border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-hidden hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
className,
|
||||
)}
|
||||
disabled={isLoading || isDisabled}
|
||||
|
||||
@@ -81,10 +81,9 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
redirectUrl = new URL(authRedirectUrl, window.location.origin);
|
||||
}
|
||||
|
||||
redirectUrl.searchParams.set(
|
||||
FIRST_LOGIN_PARAM,
|
||||
response?.isNewUser ? '1' : '0',
|
||||
);
|
||||
if (response?.isNewUser) {
|
||||
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
|
||||
}
|
||||
|
||||
const shouldTriggerPurchase =
|
||||
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
|
||||
@@ -153,7 +152,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
|
||||
<>
|
||||
<button
|
||||
className={cn(
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
'inline-flex h-10 w-full items-center justify-center gap-2 rounded-sm border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-hidden hover:border-gray-400 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
|
||||
className,
|
||||
)}
|
||||
disabled={isLoading || isDisabled}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function ResetPasswordForm() {
|
||||
<form className="mx-auto w-full" onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="password"
|
||||
className="mb-2 mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mb-2 mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-xs outline-hidden transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
required
|
||||
minLength={6}
|
||||
placeholder="New Password"
|
||||
@@ -71,7 +71,7 @@ export function ResetPasswordForm() {
|
||||
|
||||
<input
|
||||
type="password"
|
||||
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-sm outline-none transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
className="mt-2 block w-full appearance-none rounded-lg border border-gray-300 px-3 py-2 shadow-xs outline-hidden transition duration-150 ease-in-out placeholder:text-gray-400 focus:ring-2 focus:ring-black focus:ring-offset-1"
|
||||
required
|
||||
minLength={6}
|
||||
placeholder="Confirm New Password"
|
||||
@@ -88,7 +88,7 @@ export function ResetPasswordForm() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="mt-2 inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-none focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
className="mt-2 inline-flex w-full items-center justify-center rounded-lg bg-black p-2 py-3 text-sm font-medium text-white outline-hidden focus:ring-2 focus:ring-black focus:ring-offset-1 disabled:bg-gray-400"
|
||||
>
|
||||
{isLoading ? 'Please wait...' : 'Reset Password'}
|
||||
</button>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import {
|
||||
FIRST_LOGIN_PARAM,
|
||||
TOKEN_COOKIE_NAME,
|
||||
setAuthToken,
|
||||
} from '../../lib/jwt';
|
||||
import { FIRST_LOGIN_PARAM, TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
|
||||
import { Spinner } from '../ReactIcons/Spinner';
|
||||
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
|
||||
import { triggerUtmRegistration } from '../../lib/browser.ts';
|
||||
@@ -36,10 +32,9 @@ export function TriggerVerifyAccount() {
|
||||
setAuthToken(response.token);
|
||||
|
||||
const url = new URL('/', window.location.origin);
|
||||
url.searchParams.set(
|
||||
FIRST_LOGIN_PARAM,
|
||||
response?.isNewUser ? '1' : '0',
|
||||
);
|
||||
if (response?.isNewUser) {
|
||||
url.searchParams.set(FIRST_LOGIN_PARAM, '1');
|
||||
}
|
||||
window.location.href = url.toString();
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -125,7 +125,7 @@ export function Befriend() {
|
||||
<div>
|
||||
<a
|
||||
href="/"
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 px-3 py-2 text-center"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 px-3 py-2 text-center"
|
||||
>
|
||||
Back to home
|
||||
</a>
|
||||
@@ -141,7 +141,7 @@ export function Befriend() {
|
||||
const isMe = currentUser?.id === user.id;
|
||||
|
||||
return (
|
||||
<div className="container !max-w-[400px] text-center">
|
||||
<div className="container max-w-[400px]! text-center">
|
||||
<img
|
||||
alt={'join team'}
|
||||
src={userAvatar}
|
||||
@@ -169,7 +169,7 @@ export function Befriend() {
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
className="w-full flex-grow cursor-pointer rounded-lg bg-black px-3 py-2 text-center text-white disabled:cursor-not-allowed disabled:opacity-40"
|
||||
className="w-full grow cursor-pointer rounded-lg bg-black px-3 py-2 text-center text-white disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
{isMe ? "You can't add yourself" : 'Add Friend'}
|
||||
</button>
|
||||
@@ -177,7 +177,7 @@ export function Befriend() {
|
||||
|
||||
{user.status === 'sent' && (
|
||||
<>
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<CheckIcon additionalClasses="mr-2 h-4 w-4" />
|
||||
Request Sent
|
||||
</span>
|
||||
@@ -188,7 +188,7 @@ export function Befriend() {
|
||||
setIsConfirming(true);
|
||||
}}
|
||||
type="button"
|
||||
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
|
||||
className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
|
||||
>
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
|
||||
Withdraw Request
|
||||
@@ -196,7 +196,7 @@ export function Befriend() {
|
||||
)}
|
||||
|
||||
{isConfirming && (
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
Are you sure?{' '}
|
||||
<button
|
||||
className="ml-2 text-red-700 underline"
|
||||
@@ -225,7 +225,7 @@ export function Befriend() {
|
||||
|
||||
{user.status === 'accepted' && (
|
||||
<>
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<AddedUserIcon additionalClasses="mr-2 h-5 w-5" />
|
||||
You are friends
|
||||
</span>
|
||||
@@ -236,7 +236,7 @@ export function Befriend() {
|
||||
setIsConfirming(true);
|
||||
}}
|
||||
type="button"
|
||||
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
|
||||
className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
|
||||
>
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
|
||||
Remove Friend
|
||||
@@ -244,7 +244,7 @@ export function Befriend() {
|
||||
)}
|
||||
|
||||
{isConfirming && (
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
Are you sure?{' '}
|
||||
<button
|
||||
className="ml-2 text-red-700 underline"
|
||||
@@ -271,12 +271,12 @@ export function Befriend() {
|
||||
|
||||
{user.status === 'rejected' && (
|
||||
<>
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-gray-300 px-3 py-2 text-center text-black">
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-4 w-4" />
|
||||
Request Rejected
|
||||
</span>
|
||||
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
Changed your mind?{' '}
|
||||
<button
|
||||
className="ml-2 text-red-700 underline"
|
||||
@@ -296,7 +296,7 @@ export function Befriend() {
|
||||
|
||||
{user.status === 'got_rejected' && (
|
||||
<>
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-500 px-3 py-2 text-center text-red-500">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-red-500 px-3 py-2 text-center text-red-500">
|
||||
<StopIcon additionalClasses="mr-2 h-4 w-4" />
|
||||
Request Rejected
|
||||
</span>
|
||||
@@ -311,7 +311,7 @@ export function Befriend() {
|
||||
pageProgressMessage.set('');
|
||||
});
|
||||
}}
|
||||
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-gray-800 bg-gray-800 px-3 py-2 text-center text-white hover:bg-black"
|
||||
className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-gray-800 bg-gray-800 px-3 py-2 text-center text-white hover:bg-black"
|
||||
>
|
||||
<CheckIcon additionalClasses="mr-2 h-4 w-4" />
|
||||
Accept Request
|
||||
@@ -323,7 +323,7 @@ export function Befriend() {
|
||||
setIsConfirming(true);
|
||||
}}
|
||||
type="button"
|
||||
className="flex w-full flex-grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-white px-3 py-2 text-center text-red-600 hover:bg-red-100"
|
||||
className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-white px-3 py-2 text-center text-red-600 hover:bg-red-100"
|
||||
>
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-[19px] w-[19px]" />
|
||||
Reject Request
|
||||
@@ -331,7 +331,7 @@ export function Befriend() {
|
||||
)}
|
||||
|
||||
{isConfirming && (
|
||||
<span className="flex w-full flex-grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
<span className="flex w-full grow cursor-default items-center justify-center rounded-lg border border-red-600 px-3 py-2.5 text-center text-sm text-red-600">
|
||||
Are you sure?{' '}
|
||||
<button
|
||||
className="ml-2 text-red-700 underline"
|
||||
|
||||
@@ -27,7 +27,7 @@ const isBestPracticeReady = !isUpcoming;
|
||||
<MarkFavorite
|
||||
resourceId={bestPracticeId}
|
||||
resourceType="best-practice"
|
||||
className="text-gray-500 !opacity-100 hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 hover:[&>svg]:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-5 sm:[&>svg]:w-5 ml-1.5 relative focus:outline-0"
|
||||
className="text-gray-500 opacity-100! hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 [&>svg]:hover:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-5 sm:[&>svg]:w-5 ml-1.5 relative focus:outline-0"
|
||||
client:load
|
||||
/>
|
||||
</h1>
|
||||
|
||||
@@ -17,11 +17,11 @@ const formattedDate = DateTime.fromISO(frontmatter.date).toFormat(
|
||||
|
||||
<div class='relative mb-6' id={changelog.id}>
|
||||
<span
|
||||
class='absolute -left-6 top-2 h-2 w-2 flex-shrink-0 rounded-full bg-gray-300'
|
||||
class='absolute -left-6 top-2 h-2 w-2 shrink-0 rounded-full bg-gray-300'
|
||||
></span>
|
||||
|
||||
<div class='mb-3 flex flex-col sm:flex-row items-start sm:items-center gap-0.5 sm:gap-2'>
|
||||
<span class='flex-shrink-0 text-xs tracking-wide text-gray-400'>
|
||||
<span class='shrink-0 text-xs tracking-wide text-gray-400'>
|
||||
{formattedDate}
|
||||
</span>
|
||||
<span class='truncate text-base font-medium text-balance'>
|
||||
|
||||
@@ -6,13 +6,13 @@ const formattedDate = DateTime.fromISO('2024-09-13').toFormat('dd LLL, yyyy');
|
||||
|
||||
<div class='relative mb-6'>
|
||||
<span
|
||||
class='absolute -left-6 top-2 h-2 w-2 flex-shrink-0 rounded-full bg-gray-300'
|
||||
class='absolute -left-6 top-2 h-2 w-2 shrink-0 rounded-full bg-gray-300'
|
||||
></span>
|
||||
|
||||
<div
|
||||
class='mb-3 flex flex-col items-start gap-0.5 sm:flex-row sm:items-center sm:gap-2'
|
||||
>
|
||||
<span class='flex-shrink-0 text-xs tracking-wide text-gray-400'>
|
||||
<span class='shrink-0 text-xs tracking-wide text-gray-400'>
|
||||
{formattedDate}
|
||||
</span>
|
||||
<span class='truncate text-balance text-base font-medium'>
|
||||
|
||||
@@ -7,7 +7,7 @@ const top10Changelogs = allChangelogs.slice(0, 10);
|
||||
---
|
||||
|
||||
<div class='border-t bg-white py-6 text-left sm:py-16 sm:text-center'>
|
||||
<div class='container !max-w-[650px]'>
|
||||
<div class='container max-w-[650px]!'>
|
||||
<p class='text-2xl font-bold sm:text-5xl'>
|
||||
<img
|
||||
src='/images/gifs/rocket.gif'
|
||||
@@ -40,10 +40,10 @@ const top10Changelogs = allChangelogs.slice(0, 10);
|
||||
href={`/changelog#${changelog.id}`}
|
||||
class='flex flex-col items-start sm:flex-row sm:items-center'
|
||||
>
|
||||
<span class='flex-shrink-0 pr-0 text-right text-sm tracking-wide text-gray-400 sm:w-[120px] sm:pr-4'>
|
||||
<span class='shrink-0 pr-0 text-right text-sm tracking-wide text-gray-400 sm:w-[120px] sm:pr-4'>
|
||||
{formattedDate}
|
||||
</span>
|
||||
<span class='hidden h-3 w-3 flex-shrink-0 rounded-full bg-gray-300 sm:block' />
|
||||
<span class='hidden h-3 w-3 shrink-0 rounded-full bg-gray-300 sm:block' />
|
||||
<span class='text-balance text-base font-medium text-gray-900 sm:pl-8'>
|
||||
{changelog.frontmatter.title}
|
||||
</span>
|
||||
|
||||
@@ -194,13 +194,13 @@ export function CommandMenu() {
|
||||
return (
|
||||
<div className="fixed left-0 right-0 top-0 z-50 flex h-full justify-center overflow-y-auto overflow-x-hidden bg-black/50">
|
||||
<div className="relative top-0 h-full w-full max-w-lg p-2 sm:mt-20 md:h-auto">
|
||||
<div className="relative rounded-lg bg-white shadow" ref={modalRef}>
|
||||
<div className="relative rounded-lg bg-white shadow-sm" ref={modalRef}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
autoFocus={true}
|
||||
type="text"
|
||||
value={searchedText}
|
||||
className="w-full rounded-t-md border-b p-4 text-sm focus:bg-gray-50 focus:outline-none"
|
||||
className="w-full rounded-t-md border-b p-4 text-sm focus:bg-gray-50 focus:outline-hidden"
|
||||
placeholder="Search roadmaps, guides or pages .."
|
||||
autoComplete="off"
|
||||
onInput={(e) => {
|
||||
@@ -249,7 +249,7 @@ export function CommandMenu() {
|
||||
)}
|
||||
<a
|
||||
className={cn(
|
||||
'flex w-full items-center rounded p-2 text-sm',
|
||||
'flex w-full items-center rounded-sm p-2 text-sm',
|
||||
counter === activeCounter ? 'bg-gray-100' : '',
|
||||
)}
|
||||
onMouseOver={() => setActiveCounter(counter)}
|
||||
|
||||
@@ -68,11 +68,11 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 right-0 top-0 z-[100] h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
|
||||
<div className="fixed left-0 right-0 top-0 z-100 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
|
||||
<div className="relative mx-auto h-full w-full max-w-2xl p-4 md:h-auto">
|
||||
<div
|
||||
ref={popupBodyEl}
|
||||
className="popup-body relative mt-4 overflow-hidden rounded-lg bg-white shadow"
|
||||
className="popup-body relative mt-4 overflow-hidden rounded-lg bg-white shadow-sm"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -86,7 +86,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
|
||||
ref={searchInputEl}
|
||||
type="text"
|
||||
placeholder="Search roadmaps"
|
||||
className="block w-full border-b px-5 pb-3.5 pt-4 outline-none placeholder:text-gray-400"
|
||||
className="block w-full border-b px-5 pb-3.5 pt-4 outline-hidden placeholder:text-gray-400"
|
||||
value={searchText}
|
||||
onInput={(e) => setSearchText((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
|
||||
@@ -76,7 +76,7 @@ export function Step0(props: Step0Props) {
|
||||
{validTeamTypes.map((validTeamType) => (
|
||||
<button
|
||||
key={validTeamType.value}
|
||||
className={`flex flex-grow flex-col items-center rounded-lg border px-5 pb-10 pt-12 ${
|
||||
className={`flex grow flex-col items-center rounded-lg border px-5 pb-10 pt-12 ${
|
||||
validTeamType.value == selectedTeamType
|
||||
? 'border-gray-400 bg-gray-100'
|
||||
: 'border-gray-300 hover:border-gray-400 hover:bg-gray-50'
|
||||
|
||||
@@ -50,7 +50,7 @@ export function Step2(props: Step2Props) {
|
||||
onClick={onNext}
|
||||
disabled={teamResourceConfig.length !== 0}
|
||||
className={
|
||||
'flex-grow rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black md:flex-auto disabled:opacity-50 disabled:pointer-events-none'
|
||||
'grow rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black md:flex-auto disabled:opacity-50 disabled:pointer-events-none'
|
||||
}
|
||||
>
|
||||
Skip for Now
|
||||
|
||||
@@ -109,7 +109,7 @@ export function Step3(props: Step3Props) {
|
||||
|
||||
setUsers(newUsers);
|
||||
}}
|
||||
className="flex-grow rounded-md border border-gray-200 bg-white px-4 py-2 text-gray-900"
|
||||
className="grow rounded-md border border-gray-200 bg-white px-4 py-2 text-gray-900"
|
||||
/>
|
||||
<RoleDropdown
|
||||
selectedRole={user.role}
|
||||
@@ -180,7 +180,7 @@ export function Step3(props: Step3Props) {
|
||||
onClick={onNext}
|
||||
disabled={users.filter((u) => u.email).length !== 0}
|
||||
className={
|
||||
'rounded-md flex-grow md:flex-auto border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black disabled:opacity-50 disabled:pointer-events-none'
|
||||
'rounded-md grow md:flex-auto border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black disabled:opacity-50 disabled:pointer-events-none'
|
||||
}
|
||||
>
|
||||
Skip for Now
|
||||
|
||||
@@ -148,12 +148,12 @@ export function UpdateTeamResourceModal(props: ProgressMapProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 right-0 top-0 z-[100] h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
|
||||
<div className="fixed left-0 right-0 top-0 z-100 h-full items-center justify-center overflow-y-auto overflow-x-hidden overscroll-contain bg-black/50">
|
||||
<div className="relative mx-auto h-full w-full max-w-4xl p-4 md:h-auto">
|
||||
<div
|
||||
id={'customized-roadmap'}
|
||||
ref={popupBodyEl}
|
||||
className="popup-body relative rounded-lg bg-white shadow"
|
||||
className="popup-body relative rounded-lg bg-white shadow-sm"
|
||||
>
|
||||
<div
|
||||
className={
|
||||
|
||||
@@ -53,7 +53,7 @@ export function EmbedRoadmapModal(props: ShareRoadmapModalProps) {
|
||||
<div className="flex items-center justify-between px-4 pb-4 pt-2">
|
||||
<button
|
||||
className={cn(
|
||||
'flex h-9 w-full items-center justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white outline-none',
|
||||
'flex h-9 w-full items-center justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white outline-hidden',
|
||||
{
|
||||
'bg-green-500 hover:bg-green-600 focus:bg-green-600': isCopied,
|
||||
'bg-gray-900 hover:bg-gray-800 focus:bg-gray-800': !isCopied,
|
||||
|
||||
@@ -34,7 +34,7 @@ export function PersonalRoadmapActionDropdown(
|
||||
<button
|
||||
disabled={false}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-1 rounded-md border border-gray-300 bg-white px-2 py-1.5 text-xs hover:bg-gray-50 focus:outline-none sm:hidden"
|
||||
className="flex items-center gap-1 rounded-md border border-gray-300 bg-white px-2 py-1.5 text-xs hover:bg-gray-50 focus:outline-hidden sm:hidden"
|
||||
>
|
||||
<MoreVertical size={14} />
|
||||
Options
|
||||
@@ -53,7 +53,7 @@ export function PersonalRoadmapActionDropdown(
|
||||
setIsOpen(false);
|
||||
onUpdateSharing();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<Lock size={14} className="mr-2" />
|
||||
Sharing
|
||||
@@ -67,7 +67,7 @@ export function PersonalRoadmapActionDropdown(
|
||||
setIsOpen(false);
|
||||
onCustomize();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<Shapes size={14} className="mr-2" />
|
||||
Customize
|
||||
@@ -81,7 +81,7 @@ export function PersonalRoadmapActionDropdown(
|
||||
setIsOpen(false);
|
||||
onDelete();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<Trash2 size={14} className="mr-2" />
|
||||
Delete
|
||||
|
||||
@@ -176,7 +176,7 @@ function CustomRoadmapItem(props: CustomRoadmapItemProps) {
|
||||
<a
|
||||
href={editorLink}
|
||||
className={
|
||||
'ml-2 flex items-center gap-2 rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-black hover:bg-gray-50 focus:outline-none'
|
||||
'ml-2 flex items-center gap-2 rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-black hover:bg-gray-50 focus:outline-hidden'
|
||||
}
|
||||
target={'_blank'}
|
||||
>
|
||||
@@ -186,7 +186,7 @@ function CustomRoadmapItem(props: CustomRoadmapItemProps) {
|
||||
<a
|
||||
href={`/r/${roadmap?.slug}`}
|
||||
className={
|
||||
'ml-2 flex items-center gap-2 rounded-md border border-blue-400 bg-white px-2 py-1.5 text-xs text-blue-600 hover:bg-blue-50 focus:outline-none'
|
||||
'ml-2 flex items-center gap-2 rounded-md border border-blue-400 bg-white px-2 py-1.5 text-xs text-blue-600 hover:bg-blue-50 focus:outline-hidden'
|
||||
}
|
||||
target={'_blank'}
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function RoadmapActionButton(props: RoadmapActionButtonProps) {
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="align-right absolute right-0 top-full z-[9999] mt-1 w-[140px] rounded-md bg-slate-800 px-2 py-2 text-white shadow-md"
|
||||
className="align-right absolute right-0 top-full z-9999 mt-1 w-[140px] rounded-md bg-slate-800 px-2 py-2 text-white shadow-md"
|
||||
>
|
||||
<ul>
|
||||
{onCustomize && (
|
||||
@@ -42,7 +42,7 @@ export function RoadmapActionButton(props: RoadmapActionButtonProps) {
|
||||
setIsOpen(false);
|
||||
onCustomize();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<PenSquare size={14} className="mr-2" />
|
||||
Edit
|
||||
@@ -56,7 +56,7 @@ export function RoadmapActionButton(props: RoadmapActionButtonProps) {
|
||||
setIsOpen(false);
|
||||
onUpdateSharing();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<Lock size={14} className="mr-2" />
|
||||
Sharing
|
||||
@@ -70,7 +70,7 @@ export function RoadmapActionButton(props: RoadmapActionButtonProps) {
|
||||
setIsOpen(false);
|
||||
onDelete();
|
||||
}}
|
||||
className="flex w-full cursor-pointer items-center rounded p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
|
||||
>
|
||||
<Trash2 size={14} className="mr-2" />
|
||||
Delete
|
||||
|
||||
@@ -128,7 +128,7 @@ export function ShareRoadmapModal(props: ShareRoadmapModalProps) {
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<button
|
||||
disabled={isLoading}
|
||||
className="flex h-9 items-center rounded-md border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-black outline-none hover:border-gray-300 hover:bg-gray-50 focus:border-gray-300 focus:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
className="flex h-9 items-center rounded-md border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-black outline-hidden hover:border-gray-300 hover:bg-gray-50 focus:border-gray-300 focus:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
onClick={onClose}
|
||||
>
|
||||
{isLoading ? (
|
||||
@@ -141,7 +141,7 @@ export function ShareRoadmapModal(props: ShareRoadmapModalProps) {
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="flex h-9 items-center justify-center rounded-md border border-transparent bg-gray-900 px-4 py-2 text-sm font-medium text-white outline-none hover:bg-gray-800 focus:bg-gray-800"
|
||||
className="flex h-9 items-center justify-center rounded-md border border-transparent bg-gray-900 px-4 py-2 text-sm font-medium text-white outline-hidden hover:bg-gray-800 focus:bg-gray-800"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{isCopied ? (
|
||||
|
||||
@@ -92,7 +92,7 @@ export function SharedRoadmapList(props: SharedRoadmapListProps) {
|
||||
>
|
||||
<a
|
||||
href={`/r/=${roadmap?.slug}`}
|
||||
className="group inline-grid w-full grid-cols-[auto,16px] items-center justify-between gap-2 px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-black"
|
||||
className="group inline-grid w-full grid-cols-[auto_16px] items-center justify-between gap-2 px-3 py-2 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-black"
|
||||
target={'_blank'}
|
||||
>
|
||||
<span className="w-full truncate">
|
||||
|
||||
@@ -79,7 +79,7 @@ export function SubmitShowcaseWarning(props: SubmitShowcaseWarningProps) {
|
||||
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center text-sm hover:bg-gray-300"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center text-sm hover:bg-gray-300"
|
||||
onClick={onClose}
|
||||
disabled={submit.isPending}
|
||||
>
|
||||
|
||||
@@ -62,7 +62,7 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
|
||||
<a
|
||||
key={roadmap.id}
|
||||
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"
|
||||
className="relative truncate rounded-md border bg-white p-2.5 text-left text-sm shadow-xs hover:border-gray-400 hover:bg-gray-50"
|
||||
>
|
||||
{roadmap.title}
|
||||
</a>
|
||||
|
||||
@@ -15,7 +15,7 @@ export function DashboardCardLink(props: DashboardCardLinkProps) {
|
||||
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',
|
||||
'relative mt-4 flex min-h-[220px] flex-col justify-end rounded-lg border border-gray-300 bg-linear-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}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function DashboardCustomProgressCard(props: DashboardCustomProgressCardPr
|
||||
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"
|
||||
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-xs transition-all hover:border-gray-400 hover:bg-gray-50"
|
||||
>
|
||||
<h4 className="truncate font-medium text-gray-900">{resourceTitle}</h4>
|
||||
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { cn } from '../../../editor/utils/classname';
|
||||
import { useParams } from '../../hooks/use-params';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { getUser } from '../../lib/jwt';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { $teamList } from '../../stores/team';
|
||||
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown';
|
||||
import { DashboardTabButton } from './DashboardTabButton';
|
||||
import { DashboardTab } from './DashboardTab';
|
||||
import { PersonalDashboard, type BuiltInRoadmap } from './PersonalDashboard';
|
||||
import { TeamDashboard } from './TeamDashboard';
|
||||
import type { QuestionGroupType } from '../../lib/question-group';
|
||||
import type { GuideFileType } from '../../lib/guide';
|
||||
import type { VideoFileType } from '../../lib/video';
|
||||
import { getUser } from '../../lib/jwt';
|
||||
import { useParams } from '../../hooks/use-params';
|
||||
|
||||
type DashboardPageProps = {
|
||||
builtInRoleRoadmaps?: BuiltInRoadmap[];
|
||||
builtInSkillRoadmaps?: BuiltInRoadmap[];
|
||||
builtInBestPractices?: BuiltInRoadmap[];
|
||||
isTeamPage?: boolean;
|
||||
questionGroups?: QuestionGroupType[];
|
||||
guides?: GuideFileType[];
|
||||
videos?: VideoFileType[];
|
||||
};
|
||||
|
||||
export function DashboardPage(props: DashboardPageProps) {
|
||||
@@ -30,9 +23,6 @@ export function DashboardPage(props: DashboardPageProps) {
|
||||
builtInBestPractices,
|
||||
builtInSkillRoadmaps,
|
||||
isTeamPage = false,
|
||||
questionGroups,
|
||||
guides,
|
||||
videos,
|
||||
} = props;
|
||||
|
||||
const currentUser = getUser();
|
||||
@@ -76,79 +66,78 @@ export function DashboardPage(props: DashboardPageProps) {
|
||||
: '/images/default-avatar.png';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('bg-slate-900', {
|
||||
'striped-loader-slate': isLoading,
|
||||
})}
|
||||
>
|
||||
<div className="bg-slate-800/30 py-5 min-h-[70px]">
|
||||
<div className="container flex flex-wrap items-center gap-1.5">
|
||||
{!isLoading && (
|
||||
<>
|
||||
<DashboardTabButton
|
||||
label="Personal"
|
||||
isActive={!selectedTeamId && !isTeamPage}
|
||||
href="/dashboard"
|
||||
avatar={userAvatar}
|
||||
/>
|
||||
{teamList.map((team) => {
|
||||
const { avatar } = team;
|
||||
const avatarUrl = avatar
|
||||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
|
||||
: '/images/default-avatar.png';
|
||||
return (
|
||||
<DashboardTabButton
|
||||
key={team._id}
|
||||
label={team.name}
|
||||
isActive={team._id === selectedTeamId}
|
||||
{...(team.status === 'invited'
|
||||
? {
|
||||
href: `/respond-invite?i=${team.memberId}`,
|
||||
}
|
||||
: {
|
||||
href: `/team?t=${team._id}`,
|
||||
})}
|
||||
avatar={avatarUrl}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<DashboardTabButton
|
||||
label="+ Create Team"
|
||||
isActive={false}
|
||||
href="/team/new"
|
||||
className="border border-dashed border-slate-700 bg-transparent px-3 text-[13px] text-sm text-gray-500 hover:border-solid hover:border-slate-700 hover:text-gray-400"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
|
||||
<div className="container">
|
||||
<div className="mb-6 flex flex-wrap items-center gap-1.5 sm:mb-8">
|
||||
<DashboardTab
|
||||
label="Personal"
|
||||
isActive={!selectedTeamId && !isTeamPage}
|
||||
href="/dashboard"
|
||||
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?t=${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>
|
||||
|
||||
<div className="">
|
||||
{!selectedTeamId && !isTeamPage && (
|
||||
<div className="bg-slate-900">
|
||||
<PersonalDashboard
|
||||
builtInRoleRoadmaps={builtInRoleRoadmaps}
|
||||
builtInSkillRoadmaps={builtInSkillRoadmaps}
|
||||
builtInBestPractices={builtInBestPractices}
|
||||
questionGroups={questionGroups}
|
||||
guides={guides}
|
||||
videos={videos}
|
||||
/>
|
||||
</div>
|
||||
<PersonalDashboard
|
||||
builtInRoleRoadmaps={builtInRoleRoadmaps}
|
||||
builtInSkillRoadmaps={builtInSkillRoadmaps}
|
||||
builtInBestPractices={builtInBestPractices}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(selectedTeamId || isTeamPage) && (
|
||||
<div className="container">
|
||||
<TeamDashboard
|
||||
builtInRoleRoadmaps={builtInRoleRoadmaps!}
|
||||
builtInSkillRoadmaps={builtInSkillRoadmaps!}
|
||||
teamId={selectedTeamId!}
|
||||
/>
|
||||
</div>
|
||||
<TeamDashboard
|
||||
builtInRoleRoadmaps={builtInRoleRoadmaps!}
|
||||
builtInSkillRoadmaps={builtInSkillRoadmaps!}
|
||||
teamId={selectedTeamId!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DashboardTabSkeleton() {
|
||||
return (
|
||||
<div className="h-[30px] w-[114px] animate-pulse rounded-md border bg-white"></div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function DashboardProgressCard(props: DashboardProgressCardProps) {
|
||||
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="grow truncate">{resourceTitle}</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{parseInt(progressPercentage, 10)}%
|
||||
</span>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function DashboardProjectCard(props: DashboardProjectCardProps) {
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full',
|
||||
'flex h-5 w-5 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',
|
||||
@@ -41,8 +41,8 @@ export function DashboardProjectCard(props: DashboardProjectCardProps) {
|
||||
/>
|
||||
)}
|
||||
</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">
|
||||
<span className="grow truncate group-hover:underline">{title.replace(/(System)|(Service)/, '')}</span>
|
||||
<span className="shrink-0 bg-transparent text-xs text-gray-400 no-underline">
|
||||
{!!startedAt &&
|
||||
status === 'started' &&
|
||||
getRelativeTimeString(startedAt)}
|
||||
|
||||
@@ -11,7 +11,7 @@ type DashboardTabProps = {
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
export function DashboardTabButton(props: DashboardTabProps) {
|
||||
export function DashboardTab(props: DashboardTabProps) {
|
||||
const { isActive, onClick, label, className, href, avatar, icon } = props;
|
||||
|
||||
const Slot = href ? 'a' : 'button';
|
||||
@@ -20,10 +20,8 @@ export function DashboardTabButton(props: DashboardTabProps) {
|
||||
<Slot
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex h-[30px] shrink-0 items-center gap-1 rounded-md border border-slate-700 bg-slate-800 p-1.5 pl-2 pr-3 text-sm leading-none text-gray-400 transition-colors hover:bg-slate-700',
|
||||
isActive
|
||||
? 'border-slate-200 bg-slate-200 text-gray-900 hover:bg-slate-200'
|
||||
: '',
|
||||
'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 } : {})}
|
||||
@@ -32,7 +30,7 @@ export function DashboardTabButton(props: DashboardTabProps) {
|
||||
<img
|
||||
src={avatar}
|
||||
alt="avatar"
|
||||
className="mr-0.5 h-4 w-4 rounded-full object-cover"
|
||||
className="h-4 w-4 mr-0.5 rounded-full object-cover"
|
||||
/>
|
||||
)}
|
||||
{icon}
|
||||
@@ -206,7 +206,7 @@ export function DashboardTeamRoadmaps(props: DashboardTeamRoadmapsProps) {
|
||||
const roadmapHeading = (
|
||||
<div className="mb-3 flex h-[20px] items-center justify-between gap-2 text-xs">
|
||||
<h2 className="uppercase text-gray-400">Roadmaps</h2>
|
||||
<span className="mx-3 h-[1px] flex-grow bg-gray-200" />
|
||||
<span className="mx-3 h-[1px] grow bg-gray-200" />
|
||||
{canManageCurrentTeam && (
|
||||
<a
|
||||
href={`/team/roadmaps?t=${teamId}`}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function EmptyStackMessage(props: EmptyStackMessageProps) {
|
||||
<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',
|
||||
'flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-xs',
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,47 +1,23 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import {
|
||||
ChartColumn,
|
||||
CheckSquare,
|
||||
FolderGit2,
|
||||
SquarePen,
|
||||
Zap,
|
||||
type LucideIcon
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { AllowedProfileVisibility } from '../../api/user.ts';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import type { GuideFileType } from '../../lib/guide';
|
||||
import { type JSXElementConstructor, useEffect, useState } from 'react';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import type { QuestionGroupType } from '../../lib/question-group';
|
||||
import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts';
|
||||
import type { VideoFileType } from '../../lib/video';
|
||||
import { $accountStreak, type StreakResponse } from '../../stores/streak';
|
||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||
import { FeaturedGuideList } from '../FeaturedGuides/FeaturedGuideList';
|
||||
import { FeaturedVideoList } from '../FeaturedVideos/FeaturedVideoList';
|
||||
import {
|
||||
FavoriteRoadmaps,
|
||||
type AIRoadmapType,
|
||||
} from '../HeroSection/FavoriteRoadmaps.tsx';
|
||||
import { HeroRoadmap } from '../HeroSection/HeroRoadmap.tsx';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
|
||||
|
||||
const projectGroups = [
|
||||
{
|
||||
title: 'Frontend',
|
||||
id: 'frontend',
|
||||
},
|
||||
{
|
||||
title: 'Backend',
|
||||
id: 'backend',
|
||||
},
|
||||
{
|
||||
title: 'DevOps',
|
||||
id: 'devops',
|
||||
},
|
||||
];
|
||||
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';
|
||||
import type { AllowedProfileVisibility } from '../../api/user.ts';
|
||||
import { PencilIcon, type LucideIcon } from 'lucide-react';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts';
|
||||
|
||||
type UserDashboardResponse = {
|
||||
name: string;
|
||||
@@ -52,7 +28,11 @@ type UserDashboardResponse = {
|
||||
profileVisibility: AllowedProfileVisibility;
|
||||
progresses: UserProgress[];
|
||||
projects: ProjectStatusDocument[];
|
||||
aiRoadmaps: AIRoadmapType[];
|
||||
aiRoadmaps: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
}[];
|
||||
topicDoneToday: number;
|
||||
};
|
||||
|
||||
@@ -62,7 +42,6 @@ export type BuiltInRoadmap = {
|
||||
title: string;
|
||||
description: string;
|
||||
isFavorite?: boolean;
|
||||
isNew?: boolean;
|
||||
relatedRoadmapIds?: string[];
|
||||
renderer?: AllowedRoadmapRenderer;
|
||||
metadata?: Record<string, any>;
|
||||
@@ -72,162 +51,16 @@ type PersonalDashboardProps = {
|
||||
builtInRoleRoadmaps?: BuiltInRoadmap[];
|
||||
builtInSkillRoadmaps?: BuiltInRoadmap[];
|
||||
builtInBestPractices?: BuiltInRoadmap[];
|
||||
questionGroups?: QuestionGroupType[];
|
||||
guides?: GuideFileType[];
|
||||
videos?: VideoFileType[];
|
||||
};
|
||||
|
||||
type DashboardStatItemProps = {
|
||||
icon: LucideIcon;
|
||||
iconClassName: string;
|
||||
value: number;
|
||||
label: string;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
function DashboardStatItem(props: DashboardStatItemProps) {
|
||||
const { icon: Icon, iconClassName, value, label, isLoading } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3',
|
||||
{
|
||||
'striped-loader-slate striped-loader-slate-fast text-transparent':
|
||||
isLoading,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
size={16}
|
||||
className={cn(iconClassName, { 'text-transparent': isLoading })}
|
||||
/>
|
||||
<span>
|
||||
<span className="tabular-nums">{value}</span> {label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ProfileButtonProps = {
|
||||
isLoading: boolean;
|
||||
name?: string;
|
||||
username?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
function PersonalProfileButton(props: ProfileButtonProps) {
|
||||
const { isLoading, name, username, avatar } = props;
|
||||
|
||||
if (isLoading || !username) {
|
||||
return (
|
||||
<a
|
||||
href="/account/update-profile"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 font-medium outline-slate-700 hover:bg-slate-800 hover:outline-slate-400',
|
||||
{
|
||||
'striped-loader-slate striped-loader-slate-fast text-transparent':
|
||||
isLoading,
|
||||
'bg-blue-500/10 text-blue-500 hover:bg-blue-500/20': !isLoading,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<CheckSquare className="h-4 w-4" strokeWidth={2.5} />
|
||||
Set up your profile
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-1.5">
|
||||
<a
|
||||
href={`/u/${username}`}
|
||||
className="flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 text-slate-300 transition-colors hover:bg-slate-800/70"
|
||||
>
|
||||
<img
|
||||
src={avatar}
|
||||
alt={name || 'Profile'}
|
||||
className="h-5 w-5 rounded-full ring-1 ring-slate-700"
|
||||
/>
|
||||
<span className="font-medium">Visit Profile</span>
|
||||
</a>
|
||||
<a
|
||||
href="/account/update-profile"
|
||||
className="flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 text-slate-400 transition-colors hover:bg-slate-800/70 hover:text-slate-300"
|
||||
title="Edit Profile"
|
||||
>
|
||||
<SquarePen className="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type DashboardStatsProps = {
|
||||
profile: ProfileButtonProps;
|
||||
accountStreak?: StreakResponse;
|
||||
topicsDoneToday?: number;
|
||||
finishedProjectsCount?: number;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
function DashboardStats(props: DashboardStatsProps) {
|
||||
const {
|
||||
accountStreak,
|
||||
topicsDoneToday = 0,
|
||||
finishedProjectsCount = 0,
|
||||
isLoading,
|
||||
profile,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="container mb-3 flex flex-col gap-4 pb-2 pt-6 text-sm text-slate-400 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex w-full flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<PersonalProfileButton
|
||||
isLoading={isLoading}
|
||||
name={profile.name}
|
||||
username={profile.username}
|
||||
avatar={profile.avatar}
|
||||
/>
|
||||
<div className="hidden flex-wrap items-center gap-2 md:flex">
|
||||
<DashboardStatItem
|
||||
icon={Zap}
|
||||
iconClassName="text-yellow-500"
|
||||
value={accountStreak?.count || 0}
|
||||
label="day streak"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<DashboardStatItem
|
||||
icon={ChartColumn}
|
||||
iconClassName="text-green-500"
|
||||
value={topicsDoneToday}
|
||||
label="learnt today"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<DashboardStatItem
|
||||
icon={FolderGit2}
|
||||
iconClassName="text-blue-500"
|
||||
value={finishedProjectsCount}
|
||||
label="projects finished"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
const {
|
||||
builtInRoleRoadmaps = [],
|
||||
builtInBestPractices = [],
|
||||
builtInSkillRoadmaps = [],
|
||||
questionGroups = [],
|
||||
guides = [],
|
||||
videos = [],
|
||||
} = props;
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [personalDashboardDetails, setPersonalDashboardDetails] =
|
||||
useState<UserDashboardResponse>();
|
||||
@@ -305,9 +138,7 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
return () => window.removeEventListener('refresh-favorites', loadProgress);
|
||||
}, []);
|
||||
|
||||
const learningRoadmapsToShow: UserProgress[] = (
|
||||
personalDashboardDetails?.progresses || []
|
||||
)
|
||||
const learningRoadmapsToShow = (personalDashboardDetails?.progresses || [])
|
||||
.filter((progress) => !progress.isCustomResource)
|
||||
.sort((a, b) => {
|
||||
const updatedAtA = new Date(a.updatedAt);
|
||||
@@ -325,10 +156,7 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
});
|
||||
|
||||
const aiGeneratedRoadmaps = personalDashboardDetails?.aiRoadmaps || [];
|
||||
|
||||
const customRoadmaps: UserProgress[] = (
|
||||
personalDashboardDetails?.progresses || []
|
||||
)
|
||||
const customRoadmaps = (personalDashboardDetails?.progresses || [])
|
||||
.filter((progress) => progress.isCustomResource)
|
||||
.sort((a, b) => {
|
||||
const updatedAtA = new Date(a.updatedAt);
|
||||
@@ -341,6 +169,43 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
? `${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(
|
||||
@@ -367,200 +232,165 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
const { username } = personalDashboardDetails || {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DashboardStats
|
||||
profile={{
|
||||
name,
|
||||
username,
|
||||
avatar: avatarLink,
|
||||
isLoading,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
accountStreak={accountStreak}
|
||||
topicsDoneToday={personalDashboardDetails?.topicDoneToday}
|
||||
finishedProjectsCount={
|
||||
enrichedProjects?.filter((p) => p.submittedAt && p.repositoryUrl)
|
||||
.length
|
||||
}
|
||||
/>
|
||||
<section>
|
||||
{isLoading ? (
|
||||
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
|
||||
) : (
|
||||
<div className="flex flex-col items-start justify-between gap-1 sm:flex-row sm:items-center">
|
||||
<h2 className="text-lg font-medium">
|
||||
Hi {name}, good {getCurrentPeriod()}!
|
||||
</h2>
|
||||
<a
|
||||
href="/home"
|
||||
className="rounded-full bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
|
||||
>
|
||||
Visit Homepage
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FavoriteRoadmaps
|
||||
progress={learningRoadmapsToShow}
|
||||
customRoadmaps={customRoadmaps}
|
||||
aiRoadmaps={aiGeneratedRoadmaps}
|
||||
<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={
|
||||
username ? 'View your profile' : 'Setup your profile'
|
||||
}
|
||||
href={username ? `/u/${username}` : '/account/update-profile'}
|
||||
{...(username && {
|
||||
externalLinkIcon: PencilIcon,
|
||||
externalLinkHref: '/account/update-profile',
|
||||
externalLinkText: 'Edit',
|
||||
})}
|
||||
className={
|
||||
!username
|
||||
? 'border-dashed border-gray-500 bg-gray-100 hover:border-gray-500 hover:bg-gray-200'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
|
||||
<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}
|
||||
/>
|
||||
|
||||
<div className="bg-gradient-to-b from-slate-900 to-black pb-12">
|
||||
<div className="relative mt-6 border-t border-t-[#1e293c] pt-12">
|
||||
<div className="container">
|
||||
<h2
|
||||
id="role-based-roadmaps"
|
||||
className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2"
|
||||
>
|
||||
Role Based Roadmaps
|
||||
</h2>
|
||||
<ListDashboardCustomProgress
|
||||
progresses={customRoadmaps}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
|
||||
{builtInRoleRoadmaps.map((roadmap) => {
|
||||
const roadmapProgress = learningRoadmapsToShow.find(
|
||||
(lr) => lr.resourceId === roadmap.id,
|
||||
);
|
||||
<DashboardAiRoadmaps
|
||||
roadmaps={aiGeneratedRoadmaps}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
|
||||
const percentageDone =
|
||||
(((roadmapProgress?.skipped || 0) +
|
||||
(roadmapProgress?.done || 0)) /
|
||||
(roadmapProgress?.total || 1)) *
|
||||
100;
|
||||
<RecommendedRoadmaps
|
||||
roadmaps={recommendedRoadmaps}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HeroRoadmap
|
||||
key={roadmap.id}
|
||||
resourceId={roadmap.id}
|
||||
resourceType="roadmap"
|
||||
resourceTitle={roadmap.title}
|
||||
isFavorite={roadmap.isFavorite}
|
||||
percentageDone={percentageDone}
|
||||
isNew={roadmap.isNew}
|
||||
url={`/${roadmap.id}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
type DashboardCardProps = {
|
||||
icon?: JSXElementConstructor<any>;
|
||||
imgUrl?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
externalLinkIcon?: LucideIcon;
|
||||
externalLinkText?: string;
|
||||
externalLinkHref?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function DashboardCard(props: DashboardCardProps) {
|
||||
const {
|
||||
icon: Icon,
|
||||
imgUrl,
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
externalLinkHref,
|
||||
externalLinkIcon: ExternalLinkIcon,
|
||||
externalLinkText,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={cn('relative overflow-hidden', className)}>
|
||||
<a
|
||||
href={href}
|
||||
className="flex flex-col 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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
|
||||
<div className="container">
|
||||
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
|
||||
Skill Based Roadmaps
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
|
||||
{builtInSkillRoadmaps.map((roadmap) => {
|
||||
const roadmapProgress = learningRoadmapsToShow.find(
|
||||
(lr) => lr.resourceId === roadmap.id,
|
||||
);
|
||||
|
||||
const percentageDone =
|
||||
(((roadmapProgress?.skipped || 0) +
|
||||
(roadmapProgress?.done || 0)) /
|
||||
(roadmapProgress?.total || 1)) *
|
||||
100;
|
||||
|
||||
return (
|
||||
<HeroRoadmap
|
||||
key={roadmap.id}
|
||||
resourceId={roadmap.id}
|
||||
resourceType="roadmap"
|
||||
resourceTitle={roadmap.title}
|
||||
isFavorite={roadmap.isFavorite}
|
||||
percentageDone={percentageDone}
|
||||
isNew={roadmap.isNew}
|
||||
url={`/${roadmap.id}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
|
||||
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
|
||||
<div className="container">
|
||||
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
|
||||
Project Ideas
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
|
||||
{projectGroups.map((projectGroup) => {
|
||||
return (
|
||||
<HeroRoadmap
|
||||
percentageDone={0}
|
||||
key={projectGroup.id}
|
||||
resourceId={projectGroup.id}
|
||||
resourceType="roadmap"
|
||||
resourceTitle={projectGroup.title}
|
||||
url={`/${projectGroup.id}/projects`}
|
||||
allowFavorite={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
|
||||
<div className="container">
|
||||
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
|
||||
Best Practices
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
|
||||
{builtInBestPractices.map((roadmap) => {
|
||||
const roadmapProgress = learningRoadmapsToShow.find(
|
||||
(lr) => lr.resourceId === roadmap.id,
|
||||
);
|
||||
|
||||
const percentageDone =
|
||||
(((roadmapProgress?.skipped || 0) +
|
||||
(roadmapProgress?.done || 0)) /
|
||||
(roadmapProgress?.total || 1)) *
|
||||
100;
|
||||
|
||||
return (
|
||||
<HeroRoadmap
|
||||
key={roadmap.id}
|
||||
resourceId={roadmap.id}
|
||||
resourceType="best-practice"
|
||||
resourceTitle={roadmap.title}
|
||||
isFavorite={roadmap.isFavorite}
|
||||
percentageDone={percentageDone}
|
||||
isNew={roadmap.isNew}
|
||||
url={`/best-practices/${roadmap.id}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
|
||||
<div className="container">
|
||||
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
|
||||
Questions
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
|
||||
{questionGroups.map((questionGroup) => {
|
||||
return (
|
||||
<HeroRoadmap
|
||||
percentageDone={0}
|
||||
key={questionGroup.id}
|
||||
resourceId={questionGroup.id}
|
||||
resourceType="roadmap"
|
||||
resourceTitle={questionGroup.frontmatter.briefTitle}
|
||||
url={`/questions/${questionGroup.id}`}
|
||||
allowFavorite={false}
|
||||
isNew={questionGroup.frontmatter.isNew}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 bg-gray-50 px-4 py-5 sm:gap-16 sm:px-0 sm:py-16">
|
||||
<FeaturedGuideList
|
||||
heading="Guides"
|
||||
guides={guides}
|
||||
questions={questionGroups
|
||||
.filter((questionGroup) => questionGroup.frontmatter.authorId)
|
||||
.slice(0, 7)}
|
||||
/>
|
||||
<FeaturedVideoList heading="Videos" videos={videos} />
|
||||
</div>
|
||||
{externalLinkHref && (
|
||||
<a
|
||||
href={externalLinkHref}
|
||||
className="absolute right-1 top-1 flex items-center gap-1.5 rounded-md bg-gray-200 p-1 px-2 text-xs text-gray-600 hover:bg-gray-300 hover:text-black"
|
||||
>
|
||||
{ExternalLinkIcon && <ExternalLinkIcon className="size-3" />}
|
||||
{externalLinkText}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DashboardCardSkeleton() {
|
||||
return (
|
||||
<div className="h-[128px] animate-pulse rounded-lg border border-gray-300 bg-white"></div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function TeamDashboard(props: TeamDashboardProps) {
|
||||
|
||||
<h2 className="mb-3 mt-6 flex h-[20px] items-center justify-between text-xs uppercase text-gray-400">
|
||||
Team Members
|
||||
<span className="flex-grow h-[1px] bg-gray-200 mx-3" />
|
||||
<span className="grow h-[1px] bg-gray-200 mx-3" />
|
||||
{canManageCurrentTeam && (
|
||||
<a
|
||||
href={`/team/members?t=${teamId}`}
|
||||
|
||||
@@ -10,7 +10,7 @@ import DeleteAccountPopup from "./DeleteAccountPopup.astro";
|
||||
|
||||
<button
|
||||
data-popup='delete-account-popup'
|
||||
class="mt-4 w-full rounded-lg bg-red-600 py-2 text-base font-regular text-white outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-1"
|
||||
class="mt-4 w-full rounded-lg bg-red-600 py-2 text-base font-regular text-white outline-hidden focus:ring-2 focus:ring-red-500 focus:ring-offset-1"
|
||||
>
|
||||
Delete Account
|
||||
</button>
|
||||
|
||||
@@ -53,7 +53,7 @@ export function DeleteAccountForm() {
|
||||
type="text"
|
||||
name="delete-account"
|
||||
id="delete-account"
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:border-gray-400"
|
||||
placeholder={'Type "delete" to confirm'}
|
||||
required
|
||||
autoFocus
|
||||
@@ -72,14 +72,14 @@ export function DeleteAccountForm() {
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
onClick={handleClosePopup}
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || confirmationText.toUpperCase() !== 'DELETE'}
|
||||
className="flex-grow cursor-pointer rounded-lg bg-red-500 py-2 text-white disabled:opacity-40"
|
||||
className="grow cursor-pointer rounded-lg bg-red-500 py-2 text-white disabled:opacity-40"
|
||||
>
|
||||
{isLoading ? 'Please wait ..' : 'Confirm'}
|
||||
</button>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
|
||||
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
|
||||
<div
|
||||
ref={popupBodyEl}
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow"
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow-sm"
|
||||
>
|
||||
<h2 className="text-2xl font-semibold text-black">Delete Team</h2>
|
||||
<p className="text-gray-500">
|
||||
@@ -90,7 +90,7 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
|
||||
type="text"
|
||||
name="delete-account"
|
||||
id="delete-account"
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:border-gray-400"
|
||||
placeholder={'Type "delete" to confirm'}
|
||||
required
|
||||
autoFocus
|
||||
@@ -111,7 +111,7 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
onClick={handleClosePopup}
|
||||
className="flex-grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center"
|
||||
className="grow cursor-pointer rounded-lg bg-gray-200 py-2 text-center"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -120,7 +120,7 @@ export function DeleteTeamPopup(props: DeleteTeamPopupProps) {
|
||||
disabled={
|
||||
isLoading || confirmationText.toUpperCase() !== 'DELETE'
|
||||
}
|
||||
className="flex-grow cursor-pointer rounded-lg bg-red-500 py-2 text-white disabled:opacity-40"
|
||||
className="grow cursor-pointer rounded-lg bg-red-500 py-2 text-white disabled:opacity-40"
|
||||
>
|
||||
{isLoading ? 'Please wait ..' : 'Confirm'}
|
||||
</button>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function DiscoverRoadmapSorting(props: DiscoverRoadmapSortingProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-auto relative flex flex-shrink-0 sm:min-w-[140px]"
|
||||
className="min-auto relative flex shrink-0 sm:min-w-[140px]"
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<button
|
||||
|
||||
@@ -53,7 +53,7 @@ export function SearchRoadmap(props: SearchRoadmapProps) {
|
||||
type="text"
|
||||
minLength={3}
|
||||
placeholder="Type 3 or more characters to search..."
|
||||
className="w-full rounded-md border border-gray-200 px-3 py-2 pl-9 text-sm transition-colors focus:border-black focus:outline-none"
|
||||
className="w-full rounded-md border border-gray-200 px-3 py-2 pl-9 text-sm transition-colors focus:border-black focus:outline-hidden"
|
||||
value={term}
|
||||
onChange={(e) => setTerm(e.target.value)}
|
||||
/>
|
||||
@@ -64,7 +64,7 @@ export function SearchRoadmap(props: SearchRoadmapProps) {
|
||||
)}
|
||||
</form>
|
||||
{total > 0 && (
|
||||
<p className="hidden flex-shrink-0 text-sm text-gray-500 sm:block">
|
||||
<p className="hidden shrink-0 text-sm text-gray-500 sm:block">
|
||||
{Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(total)}{' '}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function AIRoadmapsList(props: AIRoadmapsListProps) {
|
||||
className="flex min-h-[95px] flex-col rounded-md border transition-colors hover:bg-gray-100"
|
||||
target={'_blank'}
|
||||
>
|
||||
<h2 className="flex-grow px-2.5 py-2.5 text-base font-medium leading-tight">
|
||||
<h2 className="grow px-2.5 py-2.5 text-base font-medium leading-tight">
|
||||
{roadmap.title}
|
||||
</h2>
|
||||
<div className="flex items-center justify-between gap-2 px-2.5 py-2">
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ExploreAISearch(props: ExploreAISearchProps) {
|
||||
name="search"
|
||||
type="text"
|
||||
placeholder="Type 3 or more characters to search..."
|
||||
className="w-full rounded-md border border-gray-200 px-3 py-2 pl-9 text-sm transition-colors focus:border-black focus:outline-none"
|
||||
className="w-full rounded-md border border-gray-200 px-3 py-2 pl-9 text-sm transition-colors focus:border-black focus:outline-hidden"
|
||||
value={term}
|
||||
onChange={(e) => setTerm(e.target.value)}
|
||||
/>
|
||||
@@ -57,7 +57,7 @@ export function ExploreAISearch(props: ExploreAISearchProps) {
|
||||
)}
|
||||
</div>
|
||||
{total > 0 && (
|
||||
<p className="flex-shrink-0 text-sm text-gray-500 hidden sm:block">
|
||||
<p className="shrink-0 text-sm text-gray-500 hidden sm:block">
|
||||
{Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(total)}{' '}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function ExploreAISorting(props: ExploreAISortingProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-auto relative flex flex-shrink-0 sm:min-w-[140px]"
|
||||
className="min-auto relative flex shrink-0 sm:min-w-[140px]"
|
||||
ref={dropdownRef}
|
||||
>
|
||||
<button
|
||||
|
||||
@@ -6,24 +6,80 @@ import { isMobileScreen } from '../lib/is-mobile.ts';
|
||||
type FeatureAnnouncementProps = {};
|
||||
|
||||
export function FeatureAnnouncement(props: FeatureAnnouncementProps) {
|
||||
return null;
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const videoModal = (
|
||||
<Modal
|
||||
onClose={() => setIsPlaying(false)}
|
||||
bodyClassName={'h-auto overflow-hidden'}
|
||||
wrapperClassName={'md:max-w-3xl lg:max-w-4xl xl:max-w-5xl'}
|
||||
>
|
||||
<div className="text-balance bg-gradient-to-r from-gray-100 px-4 py-2 text-left text-sm md:py-3 lg:text-base">
|
||||
<span
|
||||
className="relative -top-px mr-1.5 rounded bg-blue-300 px-1.5 py-0.5 text-xs font-semibold uppercase text-gray-800"
|
||||
style={{ lineHeight: '1.5' }}
|
||||
>
|
||||
New
|
||||
</span>
|
||||
Projects are live on the{' '}
|
||||
<a
|
||||
href={'/projects'}
|
||||
className="font-medium text-blue-500 underline underline-offset-2"
|
||||
>
|
||||
several of our roadmaps
|
||||
</a>
|
||||
<span className={'hidden md:inline'}>
|
||||
{' '}
|
||||
and are coming soon on the others
|
||||
</span>
|
||||
<PartyPopper className="relative -top-[3px] ml-2 inline-block h-5 w-5 text-blue-500 md:ml-1 md:h-6 md:w-6" />
|
||||
</div>
|
||||
<div
|
||||
className="iframe-container"
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingBottom: '56.25%',
|
||||
height: 0,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/*https://www.youtube.com/embed/?playsinline=1&disablekb=1&&iv_load_policy=3&cc_load_policy=0&controls=0&rel=0&autoplay=1&mute=1&origin=https%3A%2F%2Fytch.xyz&widgetid=1*/}
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/9lS3slfJ0x0?start=31&autoplay=1&disablekb=1&rel=0&cc_load_policy=0&rel=0&autoplay=1&origin=https%3A%2F%2Froadmap.sh&widgetid=1&showinfo=0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
{isPlaying && videoModal}
|
||||
<button
|
||||
className="rounded-md border border-dashed border-purple-600 px-3 py-1.5 text-purple-400 transition-colors hover:border-purple-400 hover:text-purple-200"
|
||||
href="/courses/sql"
|
||||
onClick={() => {
|
||||
setIsPlaying(true);
|
||||
}}
|
||||
>
|
||||
<span className="relative sm:-top-[1px] mr-1 text-xs font-semibold uppercase text-white">
|
||||
<PartyPopper className="inline-block h-4 w-4 relative -top-[2px] mr-1" />
|
||||
Courses
|
||||
<PlayCircle className="inline-block h-4 w-4 relative -top-[2px] mr-1" />
|
||||
Watch
|
||||
</span>{' '}
|
||||
<span className={'hidden sm:inline'}>
|
||||
Our first paid course about SQL is now live!
|
||||
Practice your skills with projects
|
||||
</span>
|
||||
<span className={'inline text-sm sm:hidden'}>
|
||||
Our SQL course is now live!
|
||||
Build projects to skill up
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
47
src/components/FeaturedGuides.astro
Normal file
47
src/components/FeaturedGuides.astro
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
import type { GuideFileType } from '../lib/guide';
|
||||
import GuideListItem from './GuideListItem.astro';
|
||||
import { QuestionGroupType } from '../lib/question-group';
|
||||
|
||||
export interface Props {
|
||||
heading: string;
|
||||
guides: GuideFileType[];
|
||||
questions: QuestionGroupType[];
|
||||
}
|
||||
|
||||
const { heading, guides, questions = [] } = Astro.props;
|
||||
|
||||
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
|
||||
...guides,
|
||||
...questions,
|
||||
].sort((a, b) => {
|
||||
const aDate = new Date(a.frontmatter.date);
|
||||
const bDate = new Date(b.frontmatter.date);
|
||||
|
||||
return bDate.getTime() - aDate.getTime();
|
||||
});
|
||||
---
|
||||
|
||||
<div class='container'>
|
||||
<h2 class='block text-2xl font-bold sm:text-3xl'>{heading}</h2>
|
||||
|
||||
<div class='mt-3 sm:my-5'>
|
||||
{sortedGuides.map((guide) => <GuideListItem guide={guide} />)}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href='/guides'
|
||||
class='hidden rounded-full bg-linear-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline'
|
||||
>
|
||||
View All Guides →
|
||||
</a>
|
||||
|
||||
<div class='mt-3 block sm:hidden'>
|
||||
<a
|
||||
href='/guides'
|
||||
class='font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50'
|
||||
>
|
||||
View All Guides →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { GuideFileType } from '../../lib/guide';
|
||||
import type { QuestionGroupType } from '../../lib/question-group';
|
||||
import { GuideListItem } from './GuideListItem';
|
||||
|
||||
export interface FeaturedGuidesProps {
|
||||
heading: string;
|
||||
guides: GuideFileType[];
|
||||
questions: QuestionGroupType[];
|
||||
}
|
||||
|
||||
export function FeaturedGuideList(props: FeaturedGuidesProps) {
|
||||
const { heading, guides, questions = [] } = props;
|
||||
|
||||
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
|
||||
...guides,
|
||||
...questions,
|
||||
].sort((a, b) => {
|
||||
const aDate = new Date(a.frontmatter.date as string);
|
||||
const bDate = new Date(b.frontmatter.date as string);
|
||||
|
||||
return bDate.getTime() - aDate.getTime();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
|
||||
|
||||
<div className="mt-3 sm:my-5">
|
||||
{sortedGuides.map((guide) => (
|
||||
<GuideListItem key={guide.id} guide={guide} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/guides"
|
||||
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
|
||||
>
|
||||
View All Guides →
|
||||
</a>
|
||||
|
||||
<div className="mt-3 block sm:hidden">
|
||||
<a
|
||||
href="/guides"
|
||||
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
|
||||
>
|
||||
View All Guides →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { GuideFileType, GuideFrontmatter } from '../../lib/guide';
|
||||
import { type QuestionGroupType } from '../../lib/question-group';
|
||||
|
||||
export interface GuideListItemProps {
|
||||
guide: GuideFileType | QuestionGroupType;
|
||||
}
|
||||
|
||||
function isQuestionGroupType(
|
||||
guide: GuideFileType | QuestionGroupType,
|
||||
): guide is QuestionGroupType {
|
||||
return (guide as QuestionGroupType).questions !== undefined;
|
||||
}
|
||||
|
||||
export function GuideListItem(props: GuideListItemProps) {
|
||||
const { guide } = props;
|
||||
const { frontmatter, id } = guide;
|
||||
|
||||
let pageUrl = '';
|
||||
let guideType = '';
|
||||
|
||||
if (isQuestionGroupType(guide)) {
|
||||
pageUrl = `/questions/${id}`;
|
||||
guideType = 'Questions';
|
||||
} else {
|
||||
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
|
||||
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
|
||||
guideType = (frontmatter as GuideFrontmatter).type;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className="text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600"
|
||||
href={pageUrl}
|
||||
>
|
||||
<span className="text-sm transition-transform group-hover:translate-x-2 md:text-base">
|
||||
{frontmatter.title}
|
||||
|
||||
{frontmatter.isNew && (
|
||||
<span className="ml-2.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900">
|
||||
New
|
||||
<span className="hidden sm:inline">
|
||||
·
|
||||
{new Date(frontmatter.date || '').toLocaleString('default', {
|
||||
month: 'long',
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="hidden text-xs capitalize text-gray-500 sm:block">
|
||||
{guideType}
|
||||
</span>
|
||||
|
||||
<span className="block text-xs text-gray-400 sm:hidden"> »</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
35
src/components/FeaturedVideos.astro
Normal file
35
src/components/FeaturedVideos.astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import type { VideoFileType } from '../lib/video';
|
||||
import VideoListItem from './VideoListItem.astro';
|
||||
|
||||
export interface Props {
|
||||
heading: string;
|
||||
videos: VideoFileType[];
|
||||
}
|
||||
|
||||
const { heading, videos } = Astro.props;
|
||||
---
|
||||
|
||||
<div class='container'>
|
||||
<h2 class='text-2xl sm:text-3xl font-bold block'>{heading}</h2>
|
||||
|
||||
<div class='mt-3 sm:my-5'>
|
||||
{videos.map((video) => <VideoListItem video={video} />)}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href='/videos'
|
||||
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-linear-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white'
|
||||
>
|
||||
View All Videos →
|
||||
</a>
|
||||
|
||||
<div class='block sm:hidden mt-3'>
|
||||
<a
|
||||
href='/videos'
|
||||
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50'
|
||||
>
|
||||
View All Videos →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { VideoFileType } from '../../lib/video';
|
||||
import { VideoListItem } from './VideoListItem';
|
||||
|
||||
export interface FeaturedVideoListProps {
|
||||
heading: string;
|
||||
videos: VideoFileType[];
|
||||
}
|
||||
|
||||
export function FeaturedVideoList(props: FeaturedVideoListProps) {
|
||||
const { heading, videos } = props;
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
|
||||
|
||||
<div className="mt-3 sm:my-5">
|
||||
{videos.map((video) => (
|
||||
<VideoListItem key={video.id} video={video} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="/videos"
|
||||
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
|
||||
>
|
||||
View All Videos →
|
||||
</a>
|
||||
|
||||
<div className="mt-3 block sm:hidden">
|
||||
<a
|
||||
href="/videos"
|
||||
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
|
||||
>
|
||||
View All Videos →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { VideoFileType } from '../../lib/video';
|
||||
|
||||
export interface VideoListItemProps {
|
||||
video: VideoFileType;
|
||||
}
|
||||
|
||||
export function VideoListItem(props: VideoListItemProps) {
|
||||
const { video } = props;
|
||||
const { frontmatter, id } = video;
|
||||
|
||||
return (
|
||||
<a
|
||||
className="block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b"
|
||||
href={`/videos/${id}`}
|
||||
>
|
||||
<span className="group-hover:translate-x-2 transition-transform">
|
||||
{frontmatter.title}
|
||||
|
||||
{frontmatter.isNew && (
|
||||
<span className="bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5">
|
||||
New
|
||||
<span className="hidden sm:inline">
|
||||
·
|
||||
{new Date(frontmatter.date).toLocaleString('default', {
|
||||
month: 'long',
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="capitalize text-gray-500 text-xs hidden sm:block">
|
||||
{frontmatter.duration}
|
||||
</span>
|
||||
|
||||
<span className="text-gray-400 text-xs block sm:hidden"> »</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
|
||||
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
|
||||
<div
|
||||
ref={popupBodyEl}
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow"
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow-sm"
|
||||
>
|
||||
{!isSuccess && (
|
||||
<>
|
||||
@@ -84,7 +84,7 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
|
||||
ref={inputEl}
|
||||
name="submit-feedback"
|
||||
id="submit-feedback"
|
||||
className="mt-2 block min-h-[150px] w-full resize-none rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400"
|
||||
className="mt-2 block min-h-[150px] w-full resize-none rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400"
|
||||
placeholder="Enter your feedback"
|
||||
required
|
||||
autoFocus
|
||||
@@ -105,14 +105,14 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
|
||||
type="button"
|
||||
disabled={isLoading}
|
||||
onClick={handleClosePopup}
|
||||
className="flex-grow cursor-pointer rounded-md bg-gray-200 py-2 text-center"
|
||||
className="grow cursor-pointer rounded-md bg-gray-200 py-2 text-center"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
disabled={isLoading}
|
||||
type="submit"
|
||||
className="flex-grow cursor-pointer rounded-md bg-black py-2 text-white disabled:opacity-40"
|
||||
className="grow cursor-pointer rounded-md bg-black py-2 text-white disabled:opacity-40"
|
||||
>
|
||||
{isLoading ? 'Please wait ..' : 'Send'}
|
||||
</button>
|
||||
@@ -133,7 +133,7 @@ export function SubmitFeedbackPopup(props: SubmitFeedbackPopupProps) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClosePopup}
|
||||
className="mt-4 w-full flex-grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
|
||||
className="mt-4 w-full grow cursor-pointer rounded-lg bg-black py-2 text-center text-white disabled:opacity-40"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
@@ -88,7 +88,7 @@ export function ProgressNudge(props: ProgressNudgeProps) {
|
||||
}}
|
||||
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
|
||||
>
|
||||
<Calendar className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
|
||||
<Calendar className="h-4 w-4 shrink-0" strokeWidth={2.5} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -97,7 +97,7 @@ export function ProgressNudge(props: ProgressNudgeProps) {
|
||||
}}
|
||||
className="group relative flex items-center gap-2 rounded-full bg-stone-900 px-3 text-sm text-yellow-400"
|
||||
>
|
||||
<X className="h-4 w-4 flex-shrink-0" strokeWidth={2.5} />
|
||||
<X className="h-4 w-4 shrink-0" strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function EmptyFriends(props: EmptyFriendsProps) {
|
||||
}}
|
||||
type="text"
|
||||
value={befriendUrl}
|
||||
className="w-full border-none bg-transparent px-1.5 outline-none"
|
||||
className="w-full border-none bg-transparent px-1.5 outline-hidden"
|
||||
readOnly
|
||||
/>
|
||||
<button
|
||||
|
||||
@@ -98,7 +98,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
|
||||
progress?.renderer,
|
||||
)
|
||||
}
|
||||
className="group relative overflow-hidden rounded-md border p-2 hover:border-gray-300 hover:text-black focus:outline-none"
|
||||
className="group relative overflow-hidden rounded-md border p-2 hover:border-gray-300 hover:text-black focus:outline-hidden"
|
||||
key={progress.resourceId}
|
||||
>
|
||||
<span className="relative z-10 flex items-center justify-between text-sm">
|
||||
@@ -186,7 +186,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
|
||||
{friend.status === 'rejected' && (
|
||||
<>
|
||||
<div
|
||||
className={'flex w-full flex-grow items-center justify-center'}
|
||||
className={'flex w-full grow items-center justify-center'}
|
||||
>
|
||||
<span className=" flex flex-col items-center text-red-500">
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
|
||||
@@ -214,7 +214,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
|
||||
{friend.status === 'got_rejected' && (
|
||||
<>
|
||||
<div
|
||||
className={'flex w-full flex-grow items-center justify-center'}
|
||||
className={'flex w-full grow items-center justify-center'}
|
||||
>
|
||||
<span className=" flex flex-col items-center text-sm text-red-500">
|
||||
<DeleteUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
|
||||
@@ -242,7 +242,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
|
||||
{friend.status === 'sent' && (
|
||||
<>
|
||||
<div
|
||||
className={'flex w-full flex-grow items-center justify-center'}
|
||||
className={'flex w-full grow items-center justify-center'}
|
||||
>
|
||||
<span className=" flex flex-col items-center text-green-500">
|
||||
<AddedUserIcon additionalClasses="mr-2 h-8 w-8 mb-1" />
|
||||
@@ -296,7 +296,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
'flex w-full flex-grow flex-col items-center justify-center px-4'
|
||||
'flex w-full grow flex-col items-center justify-center px-4'
|
||||
}
|
||||
>
|
||||
<AddUserIcon additionalClasses="mr-2 h-8 w-8 mb-1 text-green-500" />
|
||||
|
||||
@@ -27,7 +27,7 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
|
||||
<div className="relative h-full w-full max-w-md p-4 md:h-auto">
|
||||
<div
|
||||
ref={popupBodyRef}
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow"
|
||||
className="popup-body relative rounded-lg bg-white p-4 shadow-sm"
|
||||
>
|
||||
<h3 className="mb-1.5 text-xl font-medium sm:text-2xl">Invite URL</h3>
|
||||
<p className="mb-3 hidden text-sm leading-none text-gray-400 sm:block">
|
||||
@@ -37,7 +37,7 @@ export function InviteFriendPopup(props: InviteFriendPopupProps) {
|
||||
<div className="mt-4 flex flex-col gap-2 sm:mt-4">
|
||||
<input
|
||||
readOnly={true}
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
|
||||
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:border-gray-400"
|
||||
value={befriendUrl}
|
||||
onClick={(e: MouseEvent<HTMLInputElement>) => {
|
||||
(e?.target as HTMLInputElement)?.select();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user