Compare commits

..

1 Commits

Author SHA1 Message Date
Arik Chakma
b22cb4e7a9 wip: tailwind upgrade 2025-01-28 21:38:14 +06:00
813 changed files with 36950 additions and 22288 deletions

View File

@@ -3,6 +3,6 @@
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1739229597159
"lastUpdateCheck": 1738019390029
}
}

View File

@@ -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 } }'

View File

@@ -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! 🌟

View File

@@ -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()],
},
});

View File

@@ -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

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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"
}
]

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
]
},

View File

@@ -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"
}
]

View File

@@ -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 CNCFs 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"
},
{

View File

@@ -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",

View File

@@ -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"
},
{

View File

@@ -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`). Its 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

View File

@@ -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"
}
]

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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",

View File

@@ -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#",

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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');

View File

@@ -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`}

View File

@@ -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,
},

View File

@@ -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>

View File

@@ -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('_', ' ')}

View File

@@ -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]" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {};

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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) => {

View File

@@ -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"

View File

@@ -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>

View File

@@ -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'>

View File

@@ -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'>

View File

@@ -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>

View File

@@ -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)}

View File

@@ -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)}
/>

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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={

View File

@@ -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,

View File

@@ -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

View File

@@ -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'}
>

View File

@@ -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

View File

@@ -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 ? (

View File

@@ -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">

View File

@@ -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}
>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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)}

View File

@@ -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}

View File

@@ -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}`}

View File

@@ -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,
)}
>

View File

@@ -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>
);
}

View File

@@ -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}`}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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)}{' '}

View File

@@ -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">

View File

@@ -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)}{' '}

View File

@@ -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

View File

@@ -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>
</>
);
}

View 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 &rarr;
</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 &nbsp;&rarr;
</a>
</div>
</div>

View File

@@ -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 &rarr;
</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 &nbsp;&rarr;
</a>
</div>
</div>
);
}

View File

@@ -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">
&nbsp;&middot;&nbsp;
{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"> &raquo;</span>
</a>
);
}

View 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 &rarr;
</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 &nbsp;&rarr;
</a>
</div>
</div>

View File

@@ -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 &rarr;
</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 &nbsp;&rarr;
</a>
</div>
</div>
);
}

View File

@@ -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">
&middot;
{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"> &raquo;</span>
</a>
);
}

View File

@@ -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>

View File

@@ -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>
</>

View File

@@ -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

View File

@@ -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" />

View File

@@ -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