mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 02:01:57 +08:00
Compare commits
85 Commits
feat/proje
...
feat/colle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3179c95e4e | ||
|
|
f75579806b | ||
|
|
086c790837 | ||
|
|
9948e89b84 | ||
|
|
3166a02f23 | ||
|
|
e9fdde087f | ||
|
|
fdfc8e6c6b | ||
|
|
7642493369 | ||
|
|
3355b91aa0 | ||
|
|
9b865678b2 | ||
|
|
9b3ec7cc19 | ||
|
|
e78a7da1a9 | ||
|
|
8c99cb6ea8 | ||
|
|
bd4e3edf76 | ||
|
|
af132495d5 | ||
|
|
c77465aa8a | ||
|
|
4e02f2fe43 | ||
|
|
363fb3cbf5 | ||
|
|
8f92c34e2e | ||
|
|
abb4b6ff97 | ||
|
|
ba75bc3336 | ||
|
|
ba135d9f0b | ||
|
|
47bfa7724e | ||
|
|
33b617ef85 | ||
|
|
2333694e2c | ||
|
|
0aca915e21 | ||
|
|
904ba51b7b | ||
|
|
8ccc8aa17d | ||
|
|
ab759671d8 | ||
|
|
cd453e5ad0 | ||
|
|
f344571ce4 | ||
|
|
89bea259f9 | ||
|
|
06489391f5 | ||
|
|
f08566a24c | ||
|
|
acfa8c343a | ||
|
|
9f6de412c3 | ||
|
|
0998196593 | ||
|
|
9fdb9be863 | ||
|
|
ca1abffc36 | ||
|
|
8b73387f03 | ||
|
|
2eac27b03b | ||
|
|
03d92f893c | ||
|
|
8918940aa6 | ||
|
|
e620c1a686 | ||
|
|
4162a4aedd | ||
|
|
13a1c6e085 | ||
|
|
4183871a75 | ||
|
|
61c4d566c2 | ||
|
|
c49563ba16 | ||
|
|
77c47e8f03 | ||
|
|
597efd07ca | ||
|
|
9577f4d615 | ||
|
|
4f01c51232 | ||
|
|
84184724c4 | ||
|
|
024c7cbda1 | ||
|
|
951f97d5f0 | ||
|
|
c3118daa57 | ||
|
|
358402e20f | ||
|
|
6c914d1b47 | ||
|
|
3598db798c | ||
|
|
ffaba806c9 | ||
|
|
caf39819da | ||
|
|
1a98f62b38 | ||
|
|
b2e2e2c3ad | ||
|
|
387d5218b2 | ||
|
|
fa0452e9c9 | ||
|
|
915373f16d | ||
|
|
12077bb8f2 | ||
|
|
ade4b279e4 | ||
|
|
c5eaf08f6e | ||
|
|
50b342bdbf | ||
|
|
cf1e2b4d5c | ||
|
|
ce6fc83ad9 | ||
|
|
d2a44fbe75 | ||
|
|
1d772af10a | ||
|
|
4cb4c057aa | ||
|
|
8dd03f0272 | ||
|
|
6130f16b23 | ||
|
|
30edae3e6e | ||
|
|
ce48c7b594 | ||
|
|
3a24ff7f24 | ||
|
|
111c7f23ab | ||
|
|
6ccbde99fe | ||
|
|
7754f7a576 | ||
|
|
2fc86bc400 |
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"devToolbar": {
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1725962974592
|
||||
}
|
||||
}
|
||||
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="astro/client" />
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,3 +33,4 @@ tests-examples
|
||||
!/editor/readonly-editor.tsx
|
||||
!/editor/renderer/renderer.ts
|
||||
!/editor/renderer/index.tsx
|
||||
/.astro
|
||||
|
||||
@@ -50,11 +50,12 @@
|
||||
"jose": "^5.6.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.419.0",
|
||||
"luxon": "^3.5.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"nanostores": "^0.10.3",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"npm-check-updates": "^17.0.0",
|
||||
"playwright": "^1.45.3",
|
||||
"playwright": "^1.47.1",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.3.1",
|
||||
"react-calendar-heatmap": "^1.9.0",
|
||||
@@ -80,6 +81,7 @@
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/dom-to-image": "^2.6.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/prismjs": "^1.26.4",
|
||||
"@types/react-calendar-heatmap": "^1.6.7",
|
||||
"@types/turndown": "^5.0.5",
|
||||
|
||||
1610
pnpm-lock.yaml
generated
1610
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/pdfs/roadmaps/redis.pdf
Normal file
BIN
public/pdfs/roadmaps/redis.pdf
Normal file
Binary file not shown.
@@ -620,8 +620,19 @@
|
||||
},
|
||||
"e3vHFaFFMV7kI9q6yf5e9": {
|
||||
"title": "Cloud Messaging",
|
||||
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.",
|
||||
"links": []
|
||||
"description": "Firebase Cloud Messaging (FCM) is a powerful, battery-efficient messaging service that enables you to send messages reliably and securely to your Android applications. It enables you to send two types of messages: \"notification messages\" and \"data messages\". Notification messages are primarily meant for user notifications and will only be delivered when the application is in the foreground. On the other hand, data messages can handle even when the app is in the background or killed and can be used to send custom key-value pairs. FCM also supports various additional features, such as topic messaging to send messages to multiple devices subscribed to a common topic, device group messaging for sending messages to groups of user devices, and upstream messaging for sending messages from the client application to the FCM server.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Documentation",
|
||||
"url": "https://firebase.google.com/docs/cloud-messaging/android/client",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Firebase Cloud Messaging",
|
||||
"url": "https://www.youtube.com/watch?v=sioEY4tWmLI&list=PLl-K7zZEsYLkuHRCtHTpi6JYHka8oHLft",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3EEfKAd-ppIQpdQSEhbA1": {
|
||||
"title": "FireStore",
|
||||
|
||||
@@ -12,12 +12,42 @@
|
||||
},
|
||||
"DE3cMpeRYuUPw2ADtfS-3": {
|
||||
"title": "Angular Architecture",
|
||||
"description": "Visit the following resources to learn more:",
|
||||
"description": "Angular follows a modular architecture pattern, dividing the application into distinct modules, components, services, and other elements, which enhances code organization and maintainability. The key building blocks include modules, which are containers grouping related components, services, directives, and other elements to ensure proper encapsulation and reusability. Components are the building blocks of Angular applications, representing parts of the user interface with associated logic, consisting of templates, styles, and a class defining behavior. Services encapsulate reusable business logic, data manipulation, and API communication, enabling data and functionality sharing across components. Directives are HTML attributes or elements that extend HTML functionality, allowing reusable behaviors across the application. Lastly, pipes transform data before displaying it in templates, providing convenient ways to format, filter, and sort data.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Angular coding style guide",
|
||||
"url": "https://angular.dev/style-guide",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "The Ultimate Guide to Angular Architecture: Best Practices for efficient coding with Angular Framework",
|
||||
"url": "https://angulardive.com/blog/the-ultimate-guide-to-angular-architecture-best-practices-for-efficient-coding-with-angular-framework/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Modern Architectures with Angular Part 1: Strategic design with Sheriff and Standalone Components",
|
||||
"url": "https://www.angulararchitects.io/en/blog/modern-architectures-with-angular-part-1-strategic-design-with-sheriff-and-standalone-components/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Optimizing the architecture of large web applications with Angular",
|
||||
"url": "https://albertobasalo.medium.com/optimizing-the-architecture-of-large-web-applications-with-angular-79d03b01a92b",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Angular Architecture Concepts and Patterns",
|
||||
"url": "https://www.bigscal.com/blogs/frontend/angular-architecture-concepts-and-patterns/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Top 10 Angular Architecture Mistakes",
|
||||
"url": "https://angularexperts.io/blog/top-10-angular-architecture-mistakes",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Architecting Angular: A Guide to effective project structure",
|
||||
"url": "https://medium.com/@nile.bits/architecting-angular-a-guide-to-effective-project-structure-9756bae92262",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Go Roadmap",
|
||||
"url": "/golang",
|
||||
"url": "https://roadmap.sh/golang",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -170,7 +170,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated JavaScript Roadmap",
|
||||
"url": "/javascript",
|
||||
"url": "https://roadmap.sh/javascript",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -201,7 +201,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Java Roadmap",
|
||||
"url": "/java",
|
||||
"url": "https://roadmap.sh/java",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -237,7 +237,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Visit Dedicated Python Roadmap",
|
||||
"url": "/python",
|
||||
"url": "https://roadmap.sh/python",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -330,7 +330,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Learn Git & GitHub",
|
||||
"url": "/git-github",
|
||||
"url": "https://roadmap.sh/git-github",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -366,7 +366,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Learn Git & GitHub",
|
||||
"url": "/git-github",
|
||||
"url": "https://roadmap.sh/git-github",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -392,7 +392,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Learn Git & GitHub",
|
||||
"url": "/git-github",
|
||||
"url": "https://roadmap.sh/git-github",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -506,7 +506,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated PostgreSQL DBA Roadmap",
|
||||
"url": "/postgresql-dba",
|
||||
"url": "https://roadmap.sh/postgresql-dba",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -542,7 +542,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "SQL Roadmap",
|
||||
"url": "/sql",
|
||||
"url": "https://roadmap.sh/sql",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1105,7 +1105,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "GraphQL Roadmap",
|
||||
"url": "/graphql",
|
||||
"url": "https://roadmap.sh/graphql",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1967,7 +1967,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated MongoDB Roadmap",
|
||||
"url": "/mongodb",
|
||||
"url": "https://roadmap.sh/mongodb",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2907,8 +2907,19 @@
|
||||
},
|
||||
"5XGvep2qoti31bsyqNzrU": {
|
||||
"title": "Real-Time Data",
|
||||
"description": "Real-time data refers to information that is processed and made available immediately or with minimal delay, allowing users or systems to react promptly to current conditions. This type of data is essential in applications requiring immediate updates and responses, such as financial trading platforms, online gaming, real-time analytics, and monitoring systems. Real-time data processing involves capturing, analyzing, and delivering information as it is generated, often using technologies like stream processing frameworks (e.g., Apache Kafka, Apache Flink) and low-latency databases. Effective real-time data systems can handle high-speed data flows, ensuring timely and accurate decision-making.",
|
||||
"links": []
|
||||
"description": "Real-time data refers to information that is processed and made available immediately or with minimal delay, allowing users or systems to react promptly to current conditions. This type of data is essential in applications requiring immediate updates and responses, such as financial trading platforms, online gaming, real-time analytics, and monitoring systems. Real-time data processing involves capturing, analyzing, and delivering information as it is generated, often using technologies like stream processing frameworks (e.g., Apache Kafka, Apache Flink) and low-latency databases. Effective real-time data systems can handle high-speed data flows, ensuring timely and accurate decision-making.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Real-time data - Wiki",
|
||||
"url": "https://en.wikipedia.org/wiki/Real-time_data",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What is Real-time Data?",
|
||||
"url": "https://www.qlik.com/us/streaming-data/real-time-data",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osvajAJlwGI3XnX0fE-kA": {
|
||||
"title": "Long Polling",
|
||||
|
||||
@@ -1953,7 +1953,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Python Roadmap",
|
||||
"url": "/python",
|
||||
"url": "https://roadmap.sh/python",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1994,7 +1994,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Go Roadmap",
|
||||
"url": "/golang",
|
||||
"url": "https://roadmap.sh/golang",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2056,7 +2056,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated React Roadmap",
|
||||
"url": "/react",
|
||||
"url": "https://roadmap.sh/react",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2097,7 +2097,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Angular Roadmap",
|
||||
"url": "/angular",
|
||||
"url": "https://roadmap.sh/angular",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2118,7 +2118,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Vue Roadmap",
|
||||
"url": "/vue",
|
||||
"url": "https://roadmap.sh/vue",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Python Roadmap",
|
||||
"url": "/python",
|
||||
"url": "https://roadmap.sh/python",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -107,7 +107,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Go Roadmap",
|
||||
"url": "/golang",
|
||||
"url": "https://roadmap.sh/golang",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -292,7 +292,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Java Roadmap",
|
||||
"url": "/java",
|
||||
"url": "https://roadmap.sh/java",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,7 +84,7 @@
|
||||
]
|
||||
},
|
||||
"DFMR-0MbmVCCrJu0I9JWG": {
|
||||
"title": "Prespective Analytics",
|
||||
"title": "Prescriptive Analytics",
|
||||
"description": "Prescriptive analytics, a crucial type of data analytics, is essential for making data-driven decisions in business and organizational contexts. As a data analyst, the goal of prescriptive analytics is to recommend various actions using predictions on the basis of known parameters to help decision makers understand likely outcomes. Prescriptive analytics employs a blend of techniques and tools such as algorithms, machine learning, computational modelling procedures, and decision-tree structures to enable automated decision making. Therefore, prescriptive analytics not only anticipates what will happen and when it will happen, but also explains why it will happen, contributing to the significance of a data analyst’s role in an organization.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -137,6 +137,11 @@
|
||||
"title": "What is hosting?",
|
||||
"description": "Web hosting is an online service that allows you to publish your website files onto the internet. So, anyone who has access to the internet has access to your website.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Web Hosting Explained for Beginners",
|
||||
"url": "https://www.hostinger.com/tutorials/what-is-web-hosting/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "What Is Web Hosting? Explained",
|
||||
"url": "https://www.youtube.com/watch?v=htbY9-yggB0",
|
||||
@@ -542,7 +547,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated JavaScript Roadmap",
|
||||
"url": "/javascript",
|
||||
"url": "https://roadmap.sh/javascript",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -972,7 +977,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Angular Roadmap",
|
||||
"url": "/angular",
|
||||
"url": "https://roadmap.sh/angular",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -998,7 +1003,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Vue Roadmap",
|
||||
"url": "/vue",
|
||||
"url": "https://roadmap.sh/vue",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1039,7 +1044,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated React Roadmap",
|
||||
"url": "/react",
|
||||
"url": "https://roadmap.sh/react",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2063,7 +2068,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated React Roadmap",
|
||||
"url": "/react",
|
||||
"url": "https://roadmap.sh/react",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2545,7 +2550,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Flutter Roadmap",
|
||||
"url": "/flutter",
|
||||
"url": "https://roadmap.sh/flutter",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -2680,7 +2685,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Flutter Roadmap",
|
||||
"url": "/flutter",
|
||||
"url": "https://roadmap.sh/flutter",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated JavaScript Roadmap",
|
||||
"url": "/javascript",
|
||||
"url": "https://roadmap.sh/javascript",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -257,7 +257,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated React Roadmap",
|
||||
"url": "/react",
|
||||
"url": "https://roadmap.sh/react",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -650,7 +650,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated PostgreSQL DBA Roadmap",
|
||||
"url": "/postgresql-dba",
|
||||
"url": "https://roadmap.sh/postgresql-dba",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"url": "https://www.datacamp.com/blog/all-about-git",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Version Control (Git) - The Missing Semester of Your CS Education",
|
||||
"url": "https://missing.csail.mit.edu/2020/version-control/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "GUI Clients",
|
||||
"url": "https://git-scm.com/downloads/guis",
|
||||
@@ -215,6 +220,11 @@
|
||||
"title": ".gitignore",
|
||||
"description": "Ignored files are tracked in a special file named `.gitignore` that is checked in at the root of your repository. There is no explicit git ignore command: instead the `.gitignore` file must be edited and committed by hand when you have new files that you wish to ignore. `.gitignore` files contain patterns that are matched against file names in your repository to determine whether or not they should be ignored.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "gitignore - A collection of useful .gitignore templates",
|
||||
"url": "https://github.com/github/gitignore",
|
||||
"type": "opensource"
|
||||
},
|
||||
{
|
||||
"title": "gitignore Documentation",
|
||||
"url": "https://git-scm.com/docs/gitignore/en",
|
||||
@@ -1427,12 +1437,22 @@
|
||||
},
|
||||
"BKVA6Q7DXemAYjyQOA0nh": {
|
||||
"title": "git filter-branch",
|
||||
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.\n\n* Filter types: You can modify trees (e.g., removing a file or running a Perl script) or information about each commit.\n* Preserving original data: The command preserves all original commit times, merge information, and other details unless specified otherwise.\n* Rewriting specific branches: Only the positive refs mentioned in the command line are rewritten; if no filters are specified, commits are recommitted without changes.\n\nVisit the following resources to learn more:",
|
||||
"description": "You can use `git filter-branch` to rewrite Git revision history by applying custom filters on each revision.\n\n* Filter types: You can modify trees (e.g., removing a file or running a Perl script) or information about each commit.\n* Preserving original data: The command preserves all original commit times, merge information, and other details unless specified otherwise.\n* Rewriting specific branches: Only the positive refs mentioned in the command line are rewritten; if no filters are specified, commits are recommitted without changes.\n\nNotably, there exists a simpler, safer, and more powerful alternative: `git filter-repo`. This tool is actively promoted by Git and offers a streamlined approach to filtering revisions, making it a preferred choice for rewriting your Git history, especially when managing large repositories.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "git filter-branch",
|
||||
"url": "https://git-scm.com/docs/git-filter-branch",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "git filter-repo",
|
||||
"url": "https://github.com/newren/git-filter-repo",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Removing sensitive data from a repository",
|
||||
"url": "https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2199,7 +2219,7 @@
|
||||
},
|
||||
"qrdOARfqGPF9xhF6snbAn": {
|
||||
"title": "OAuth Apps",
|
||||
"description": "GitHub OAuth Apps are a way to integrate with the GitHub platform using OAuth authentication. They allow developers to create custom integrations that can automate tasks, provide real-time notifications, and build custom workflows.\n\nVisit the following resources to learn more:",
|
||||
"description": "GitHub OAuth Apps allow developers to integrate with GitHub using OAuth 2.0 authentication. They enable secure, token-based access to specific GitHub resources like repositories, issues, and pull requests. OAuth Apps can automate tasks, personalize interactions, and provide real-time notifications through webhooks, all while allowing users to approve only the necessary permissions without sharing their credentials.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Creating an OAuth app",
|
||||
|
||||
@@ -965,6 +965,11 @@
|
||||
"title": "SwiftUI",
|
||||
"description": "SwiftUI is Apple's modern declarative framework for building user interfaces across all Apple platforms. Introduced in 2019, it allows developers to create UIs using Swift code, describing the desired layout and behavior rather than implementing them imperatively. SwiftUI offers a more concise and intuitive approach to UI development, with features like automatic support for Dark Mode, dynamic type, and localization. It uses a state-driven approach, automatically updating the UI when underlying data changes. While newer than UIKit, SwiftUI is rapidly evolving and gaining adoption, offering seamless integration with UIKit when needed.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "HackingWithSwift - 100 Days of SwiftUI",
|
||||
"url": "https://www.hackingwithswift.com/100/swiftui",
|
||||
"type": "course"
|
||||
},
|
||||
{
|
||||
"title": "SwiftUI Documentation",
|
||||
"url": "https://developer.apple.com/xcode/swiftui/",
|
||||
|
||||
@@ -503,7 +503,7 @@
|
||||
},
|
||||
"R6ICrk6vjoBxx5nRGo4Jg": {
|
||||
"title": "Symbol",
|
||||
"description": "Symbols are a unique and immutable primitive data type in JavaScript, introduced in ECMAScript 6 (ES6). They are often used to create unique property keys for objects, ensuring that no property key collisions occur. Each Symbol value is unique, even if created with the same description. Symbols can be created using the Symbol() function, and their primary use case is to add hidden or special properties to objects that won’t interfere with other properties or methods.\n\nLearn more from the following resources:",
|
||||
"description": "Symbols are a unique and immutable primitive data type in JavaScript, introduced in ECMAScript 6 (ES6). They are often used to create unique property keys for objects, ensuring no property key collisions occur. Each Symbol value is distinct, even when multiple are created with the same description. Symbols can be created using the Symbol() function, and their primary use case is to add hidden or special properties to objects that won’t interfere with other properties or methods.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Symbol data type in JavaScript",
|
||||
@@ -514,6 +514,16 @@
|
||||
"title": "Symbol type",
|
||||
"url": "https://javascript.info/symbol",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Symbol",
|
||||
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Symbols in Javascript",
|
||||
"url": "https://www.youtube.com/watch?v=E5Bblr-SFbA",
|
||||
"type": "video"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Go Roadmap",
|
||||
"url": "/golang",
|
||||
"url": "https://roadmap.sh/golang",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -351,8 +351,13 @@
|
||||
},
|
||||
"UljuqA89_SlCSDWWMD_C_": {
|
||||
"title": "Spark",
|
||||
"description": "Apache Spark is an open-source distributed computing system used for big data processing and analytics. It provides an interface for programming entire clusters with implicit data parallelism and fault tolerance.\n\nVisit the following resources to learn more:",
|
||||
"description": "Apache Spark is an open-source distributed computing system designed for big data processing and analytics. It offers a unified interface for programming entire clusters, enabling efficient handling of large-scale data with built-in support for data parallelism and fault tolerance. Spark excels in processing tasks like batch processing, real-time data streaming, machine learning, and graph processing. It’s known for its speed, ease of use, and ability to process data in-memory, significantly outperforming traditional MapReduce systems. Spark is widely used in big data ecosystems for its scalability and versatility across various data processing tasks.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "ApacheSpark",
|
||||
"url": "https://spark.apache.org/documentation.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Spark By Examples",
|
||||
"url": "https://sparkbyexamples.com",
|
||||
@@ -388,7 +393,7 @@
|
||||
},
|
||||
"o6GQ3-8DgDtHzdX6yeg1w": {
|
||||
"title": "Flink",
|
||||
"description": "Apache Flink is a distributed stream processing framework that is used to process large amounts of data in real-time. It is designed to be highly scalable and fault-tolerant. Flink is built on top of the Apache Kafka messaging system and is used to process data streams in real-time.\n\nVisit the following resources to learn more:",
|
||||
"description": "Apache Flink is an open-source stream processing framework designed for real-time and batch data processing with low latency and high throughput. It supports event time processing, fault tolerance, and stateful operations, making it ideal for applications like real-time analytics, fraud detection, and event-driven systems. Flink is highly scalable, integrates with various data systems, and is widely used in industries for large-scale, real-time data processing tasks.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Apache Flink Documentation",
|
||||
@@ -399,6 +404,11 @@
|
||||
"title": "Explore top posts about Apache Flink",
|
||||
"url": "https://app.daily.dev/tags/apache-flink?ref=roadmapsh",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Apache Flink Tutorialpoint",
|
||||
"url": "https://www.tutorialspoint.com/apache_flink/apache_flink_introduction.htm",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -2085,7 +2085,58 @@
|
||||
},
|
||||
"M62lAWBOrTe99TfpFOQ-Y": {
|
||||
"title": "Common Built-in Modules",
|
||||
"description": "These are the common modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.",
|
||||
"links": []
|
||||
"description": "These are the core modules that come with `Node.js` out of the box. This module provides tools or APIs for performing out certain standard `Node.js` operations. like interacting with the file system, url parsing, or logging information to the console.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Nodejs fs module",
|
||||
"url": "https://nodejs.org/api/fs.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs url module",
|
||||
"url": "https://nodejs.org/api/url.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs console module",
|
||||
"url": "https://nodejs.org/api/console.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs util module",
|
||||
"url": "https://nodejs.org/api/util.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs events module",
|
||||
"url": "https://nodejs.org/api/events.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs os module",
|
||||
"url": "https://nodejs.org/api/os.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs worker threads module",
|
||||
"url": "https://nodejs.org/api/worker_threads.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs child process module",
|
||||
"url": "https://nodejs.org/api/child_process.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs process object",
|
||||
"url": "https://nodejs.org/api/process.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Nodejs crypto module",
|
||||
"url": "https://nodejs.org/api/crypto.html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -550,7 +550,7 @@
|
||||
"description": "PostgreSQL offers a comprehensive set of data types to cater to diverse data needs, including numeric types like `INTEGER`, `FLOAT`, and `SERIAL` for auto-incrementing fields; character types such as `VARCHAR` and `TEXT` for variable-length text; and temporal types like `DATE`, `TIME`, and `TIMESTAMP` for handling date and time data. Additionally, PostgreSQL supports `BOOLEAN` for true/false values, `ENUM` for enumerated lists, and composite types for complex structures. It also excels with `JSON` and `JSONB` for storing and querying semi-structured data, arrays for storing multiple values in a single field, and geometric types for spatial data. These data types ensure flexibility and robust data management for various applications.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "",
|
||||
"title": "PostgreSQL® Data Types: Mappings to SQL, JDBC, and Java Data Types",
|
||||
"url": "https://www.instaclustr.com/blog/postgresql-data-types-mappings-to-sql-jdbc-and-java-data-types/",
|
||||
"type": "article"
|
||||
},
|
||||
@@ -866,7 +866,7 @@
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Query Planning@",
|
||||
"title": "Query Planning",
|
||||
"url": "https://www.postgresql.org/docs/current/runtime-config-query.html",
|
||||
"type": "article"
|
||||
}
|
||||
@@ -2091,6 +2091,16 @@
|
||||
"url": "https://www.postgresql.org/docs/8.1/triggers.html",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "PostgreSQL Triggers",
|
||||
"url": "https://www.postgresqltutorial.com/postgresql-triggers/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Understanding PostgreSQL Triggers",
|
||||
"url": "https://hevodata.com/learn/postgresql-triggers/",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
"title": "Using PostgreSQL triggers to automate processes with Supabase",
|
||||
"url": "https://www.youtube.com/watch?v=0N6M5BBe9AE",
|
||||
|
||||
@@ -1120,7 +1120,7 @@
|
||||
]
|
||||
},
|
||||
"_IXXTSwQOgYzYIUuKVWNE": {
|
||||
"title": "virutalenv",
|
||||
"title": "virtualenv",
|
||||
"description": "`virtualenv` is a tool to create isolated Python environments. It creates a folder which contains all the necessary executables to use the packages that a Python project would need.\n\nLearn more about `virtualenv` by visiting the following resources:",
|
||||
"links": [
|
||||
{
|
||||
|
||||
812
public/roadmap-content/redis.json
Normal file
812
public/roadmap-content/redis.json
Normal file
@@ -0,0 +1,812 @@
|
||||
{
|
||||
"-3pADOHMDQ0H6ZKNjURyn": {
|
||||
"title": "What is Redis?",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"M-EXrTDeAEMz_IkEi-ab4": {
|
||||
"title": "In-memory Data Structure Store",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"l2aXyO3STnhbFjvUXPpm2": {
|
||||
"title": "Key-value Database",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"eHuBz_zSZK3rubn7nkd7g": {
|
||||
"title": "Cache",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"mgGJTBU8ofvOzl9gYWhnG": {
|
||||
"title": "Message Broker",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"-TjnSOY8txYrhhxRV1OIl": {
|
||||
"title": "Caching",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"bVJASI7bfSYqYnNhX83ng": {
|
||||
"title": "Real-time Analytics",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"URxGmhZHr0Y8nyrYj0gJl": {
|
||||
"title": "Session Management",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"ZCyId3aIoLv3duxoJdk2P": {
|
||||
"title": "Pub/Sub Messaging",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Fv1iGX22sApIEifM2IpJz": {
|
||||
"title": "Leaderboards and Counters",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"8uRpPJ0iD4XnQPKruQc8P": {
|
||||
"title": "Data Persistence Options",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"uVewcyaFi1Pt2Gs0KrkfA": {
|
||||
"title": "Rich Data Structures",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"5-3pd4rLDqRzMzSRVLdXh": {
|
||||
"title": "High Performance and Scalability",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"EvWiEx_AoxAht6sKxzW2l": {
|
||||
"title": "Redis vs SQL/NoSQL DBs",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"1Af5H0BgdAsRdBCNdHz5v": {
|
||||
"title": "When to choose Redis?",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Bf_kLfmy7_uflqC9N0-jt": {
|
||||
"title": "Using Package Managers",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"yBZ79s6mzGdj5AnX2H_Hy": {
|
||||
"title": "Pre-compiled Binaries",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"TDxv0q7jlZ26uZYYlneem": {
|
||||
"title": "Using Docker",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"43LvShQhmoWQ8Nye7fLkz": {
|
||||
"title": "Starting the Server",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"BOGXTjmCLo6WI6mYDsqRu": {
|
||||
"title": "Connecting using Redis CLI",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"NhcZM4nUQoSBBf_1qXi6l": {
|
||||
"title": "Basic Commands / SET, GET",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"DOdNkTY1yIMipWA2CD9xH": {
|
||||
"title": "Settings and Getting Keys",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"lV_MnUNTB2h925idX0YWk": {
|
||||
"title": "DEL",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"U84XgBFPyIbY0W5afH4cx": {
|
||||
"title": "Overview of Data Types",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"ltF4vCT9ZA2XuUuHnuGnN": {
|
||||
"title": "SET",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"mQc4H2ZMMSVjh33LJY8mK": {
|
||||
"title": "GET",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"5K9qyC4mrhXYWOC8WSq8C": {
|
||||
"title": "INCR",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"t4BXPofF8OCqH5KHwdYVh": {
|
||||
"title": "DECR",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"cPWd53BO6tm-uy4gqLdtZ": {
|
||||
"title": "APPEND",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"eJQW986HM4Wf1o1i2FnXs": {
|
||||
"title": "STRLEN",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"0v8uLWRCbAqEmKKdYaRQW": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"2_E2VwbjTgk4xxTFWfHuV": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"nS0DHhfy4wxHItgOFhulA": {
|
||||
"title": "EXPR",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Vll7VMmGWSI5XGZ9JpHyl": {
|
||||
"title": "TTL",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Kq7G89KaZZMFkrH-9WZoS": {
|
||||
"title": "LPUSH",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jC8G1o7yFj7D_PGmOIgcD": {
|
||||
"title": "RPUSH",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"voa61RTWMJD3Sk8DNJoVQ": {
|
||||
"title": "LPOP",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"brUGqWZ287EWtvl9uUbNt": {
|
||||
"title": "RPOP",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"8JTtBy6oD2wFYDizVkcVa": {
|
||||
"title": "LRANGE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"hBFEUXtuzUTzWZKp2qWaZ": {
|
||||
"title": "LINDEX",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"4oCcP9FxDJSDMHCEVBCNa": {
|
||||
"title": "LLEN",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"9KvHcS5F4Jj5ZXgIAdOQY": {
|
||||
"title": "LMOVE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"eBeEUYY-IL_CMkcm31lUL": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"XTwNCCtzXvZMdaex4gZEh": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Qgkpr9vf9d6-vUg1o8XFj": {
|
||||
"title": "Sets",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"xUKoQps69FFQrJeuhD1pz": {
|
||||
"title": "SADD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"mQ0ILns53n1By0Tq6xSZI": {
|
||||
"title": "SMEMBERS",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"WQWVL5GT_scHdgfCtI7WT": {
|
||||
"title": "SREM",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Ji5ghlcGJtlmErHFqVf3d": {
|
||||
"title": "SISMEMBER",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"5aLfNBewK4Dx017qVNO3T": {
|
||||
"title": "SINTER",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"2gZL4a9aWGKWLa89iyHTc": {
|
||||
"title": "SCARD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"6QoYa-N2BKNBaRtvNeVNm": {
|
||||
"title": "SUNION",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"JX5ajmcUmkshTO-mLF8lH": {
|
||||
"title": "SDIFF",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"2SG4Hr9Tuv6cxmGkrKjYZ": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"3hayYoSZepw7pppBubotg": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"b48EUyFGUeSjtT5fOa_m6": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Wl23Jh-ASJOQ850yjaTIU": {
|
||||
"title": "Strings",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"4-C4XqACUp4nvcMIj6djF": {
|
||||
"title": "Lists",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"wY46Qj5Inw_ClBNI9PB_2": {
|
||||
"title": "Hashes",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"BOJzn9SWad9oRRdY_ub01": {
|
||||
"title": "HSET",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"MsKg9m5jFwHM2Bzjf-vdu": {
|
||||
"title": "HGET",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"TpR33sJ-tAjeG3jpGTvYR": {
|
||||
"title": "HGETALL",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"E7xFZkqqbzokD5KGTn9zJ": {
|
||||
"title": "HDEL",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"adhLMuSmfYMRyWTwIgnyE": {
|
||||
"title": "HEXISTS",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jtVnUD-na-WffMaS8qYfu": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"QTbkWZ7BpqYmBhUivccPu": {
|
||||
"title": "Sorted Sets",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"0swsBD0sOY-o5lzibT999": {
|
||||
"title": "ZADD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"3pFChX6YIItrBz9lxu4XM": {
|
||||
"title": "ZRANGE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"OlbixGa5RmdqEt7snY04j": {
|
||||
"title": "ZRANGEBYSCORE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"m0FZDPwNE71zcwM_gUwz0": {
|
||||
"title": "ZREM",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"W4v7FIQr2k-Vbm-HdfKog": {
|
||||
"title": "ZINCRBY",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"AF_kWM4V8n5Ux06IgEVTl": {
|
||||
"title": "ZRANK",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"O-fZM_U-tW0pYtNzN_8Ax": {
|
||||
"title": "ZCOUNT",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"P6TDUCroLlEI7qePBFHIH": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"lxevY15ZyP43s_JrEqMX7": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"o6e_CwxfPoU6qkfWkwKwj": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jCaVx5wvsvuyqtwh6m8si": {
|
||||
"title": "Naming Conventions",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"UlQHqw1dbxZnAKbsWsOgU": {
|
||||
"title": "Retrieval by Pattern",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"OSIYDYPGz8Vgo9SU9GGH9": {
|
||||
"title": "Expiration",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jrgaoDnt_RxTu79hk4hCD": {
|
||||
"title": "Atomicity in Redis",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"LHlwjN3WHYUBUafzzwsWQ": {
|
||||
"title": "Pipelining",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"7JzeyTrkZ_1_yxMVrqvZU": {
|
||||
"title": "Batch Operations",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"0Q3AkE8leWAyYsww3-BHX": {
|
||||
"title": "Bitmaps",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"B-YUFhPQNdr1KZNupmR5N": {
|
||||
"title": "SETBIT",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"0HFLJfcrcSnAVTecG3P8W": {
|
||||
"title": "GETBIT",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jpcyXSSib7q4WBPmpgnXA": {
|
||||
"title": "BITCOUNT",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"tkrxArg_oYH0aQfM8NkD2": {
|
||||
"title": "BITOP",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Df1Eu7CuA-ARYii9JVvnm": {
|
||||
"title": "BITPOS",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"s7PEr-5TAm5EGJm0RSjPJ": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"cszjT3YK8oyhGpqLTQzwX": {
|
||||
"title": "HyperLogLog",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"8a4DmPZrX2xGZ7zdWxS63": {
|
||||
"title": "PFADD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"JWT30KIJQHVw0MXI5sGR6": {
|
||||
"title": "PFCOUNT",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"s50jr_XOUcxh65-tGCKf5": {
|
||||
"title": "PFMERGE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"XPeCvikPuu6EJ8UcOLGPh": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"zXs_9n2yEb_eVi0WuOQKH": {
|
||||
"title": "Streams",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"7isWhgrUA6M5IGM2U2tm4": {
|
||||
"title": "XADD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"4sKiAtX5aIL4NDsQkilNC": {
|
||||
"title": "XREAD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"CiYFuYE8XudZkR6AW2NQ7": {
|
||||
"title": "XRANGE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"DQJCMEw13lELcw_AwLfrT": {
|
||||
"title": "XLEN",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"zXlSBfa-Gi9_GhSYEzre3": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"4-z4hDKm86qQatYnmE21R": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"_NiUdVQ85qnvryI38k_vQ": {
|
||||
"title": "Geospatial Indexes",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"U3N1EgHFs1-YUaB_VrJfw": {
|
||||
"title": "GEOADD",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"OWWDLuGTbdNwME7v2jxVP": {
|
||||
"title": "GEOSEARCH",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"GNMjrLPkpTphneoQ0GoZF": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"FCbdKnkI1ZHGekT6yiGua": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"9W_jaK1DSEZHRKdPcUM7h": {
|
||||
"title": "Pub/Sub",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"55BCntuWlaQiLPqNtb-2i": {
|
||||
"title": "SUBSCRIBE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"5gkZzm2F4vu6IxUoJLYbK": {
|
||||
"title": "UNSUBSCRIBE",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"gIPo-2CNqE1BsOaDzmkCU": {
|
||||
"title": "PUBLISH",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"V-d6q-3Sf0dl5v8xiCQwl": {
|
||||
"title": "More Commands",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"MvyE_JUJej0UB9xe8Anfj": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"YHbWlKrQqptUDbaQVy0_A": {
|
||||
"title": "Transactions",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"c-y5Eck8VtSyIf8RAW9p7": {
|
||||
"title": "WATCH",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Ljy-Mc0EBBX4_vXfYZ5-4": {
|
||||
"title": "EXEC",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"U6hST1MkS16T2CHV3-Ise": {
|
||||
"title": "MULTI",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"msW0Wd2H-6FFNDnjC64t-": {
|
||||
"title": "Optimistic Locking",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Veb30QrPYNjUn13dtGbUr": {
|
||||
"title": "Lua Scripting",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"rjeq3i9oX8IGyQzo--L3c": {
|
||||
"title": "EVAL",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"3X0x_PcJGWBVPL-LSVAln": {
|
||||
"title": "EVALSHA",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"kF_nGo845XDwLkwcQt008": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"bQaek7f3dAaZfSUhwovm1": {
|
||||
"title": "Persistence Options",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"D3pZdAjwPFMRxX1-iyu5-": {
|
||||
"title": "How RDB Works?",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"_pb2DPrFUUZabKxWsuFUo": {
|
||||
"title": "Configuring Save Interval",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Z6yJwUkcDX08HoMyf1LwX": {
|
||||
"title": "Usecases / Best Practices",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"PTj6oxvpw8vP295WvAI80": {
|
||||
"title": "How AOF Works?",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"ibaZ34-laQtUyxAsERi7o": {
|
||||
"title": "AOF rewrite & compaction",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"9ToKTUqbi-NV5Wcwb21PT": {
|
||||
"title": "Truncation / Corruption",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"VvOQUO22ZF8VvDSqHENNU": {
|
||||
"title": "Usecases",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"vzp7DUpjklzIA0E9WxJQA": {
|
||||
"title": "Usecases / Best Practices",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"80035BzcB-fKCvD_3N8zE": {
|
||||
"title": "No Persistence Option",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"3S-qqOlfr60HR4VvDr4He": {
|
||||
"title": "RDB vs AOF Tradeoffs",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"S5Y26m1oHCQpB-oLCdtac": {
|
||||
"title": "Hybrid Persistence",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"rSD8nJ-uNpHJVe5Hn66h7": {
|
||||
"title": "Replication Basics",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"nci3OB1NE1zJHUPfZCOpT": {
|
||||
"title": "Redis Sentinel",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"AQiCcHS6dBAAAPloxiXub": {
|
||||
"title": "Clustering",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Qy42paiTUsO8HIwbWTMui": {
|
||||
"title": "Authentication",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"wsuKH7YwGDV6GYQbdhA4o": {
|
||||
"title": "Network Security",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"gdiWwTQg6A-BFHdQBmgmH": {
|
||||
"title": "SSL/TLS Encryption",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"q2Jw49QUWCUGIfcEC1bZI": {
|
||||
"title": "INFO",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jBtEiylcedtaE6E20Uk4V": {
|
||||
"title": "MONITOR",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"XBPwHgIsXupMsyoOFkJZ0": {
|
||||
"title": "RedisInsight",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"y5FPSAi6T-5X9SUfR58_-": {
|
||||
"title": "RedisCommander",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"xF0wQYmtwXYkrzvWg5gOO": {
|
||||
"title": "Memory Management",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Sd1ENOXSFCz1YqccXjr2A": {
|
||||
"title": "Max Memory Policy",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"yaCWw2KjX58SaPajUAb0d": {
|
||||
"title": "Slow Log Analysis",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"kgHwK4N-sfh6dHjd_D_me": {
|
||||
"title": "redis-benchmark",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"2p5RF4lVYfRvYTo1Ofm-a": {
|
||||
"title": "Monitoring",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"hLIT00Iz7rV56ZBIUhWYn": {
|
||||
"title": "Redis Modules",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"jicsfYw56VrbRUt7M8c85": {
|
||||
"title": "RedisJSON",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"_GdTXcJO8uJlhPdfrmeXG": {
|
||||
"title": "Search",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"RBr8opWSh2TKXC8Fmdg0j": {
|
||||
"title": "RedisTimeSeries",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"GwVL5CvbnHsiWb1hVh7lK": {
|
||||
"title": "RedisBloom",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"giyKPtQ-pziA064P8OQD-": {
|
||||
"title": "redis.conf",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"wXRDsNGFckXV_CSiit5sN": {
|
||||
"title": "Backup and Recovery",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"Cb-KazR4PuR86VX5oT0zi": {
|
||||
"title": "Upgrading Redis",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"nUIfTkgm3PlSiqgun1BS7": {
|
||||
"title": "Disaster Recovery",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"8lyXDuZJ-KHl4v2_8Ew1h": {
|
||||
"title": "Redis Enterprise",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"cybF72wlJyJbHLUjitLvn": {
|
||||
"title": "Active-Active geo Distribution",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"sWOFnbh2EyaHRzquz1UeF": {
|
||||
"title": "Redis on Flash",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"ujs77bV8g8-FOm5hBtZFd": {
|
||||
"title": "Security and Compliance",
|
||||
"description": "",
|
||||
"links": []
|
||||
},
|
||||
"JlLwy69eQ1bPHAOOJNqjo": {
|
||||
"title": "When to consider enterprise?",
|
||||
"description": "",
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Java Roadmap",
|
||||
"url": "/java",
|
||||
"url": "https://roadmap.sh/java",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -184,7 +184,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Python Roadmap",
|
||||
"url": "/python",
|
||||
"url": "https://roadmap.sh/python",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -246,7 +246,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated Go Roadmap",
|
||||
"url": "/golang",
|
||||
"url": "https://roadmap.sh/golang",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -282,7 +282,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated JavaScript Roadmap",
|
||||
"url": "/javascript",
|
||||
"url": "https://roadmap.sh/javascript",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
@@ -1177,7 +1177,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "Visit Dedicated React Roadmap",
|
||||
"url": "/react",
|
||||
"url": "https://roadmap.sh/react",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1258,7 +1258,7 @@
|
||||
"links": [
|
||||
{
|
||||
"title": "SQL Server Indexes",
|
||||
"url": "https://www.sqlservercentral.com/articles/sql-server-indexes",
|
||||
"url": "https://www.sqlservercentral.com/articles/introduction-to-indexes",
|
||||
"type": "article"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
"kcG4IpneJzA6di0uqTiwb": {
|
||||
"title": "CREATE Action Funnel",
|
||||
"description": "Stephen Wendell's Create Action Funnel is a UX design framework focused on converting website visitors into active customers through a systematic and engaging process. The approach emphasizes on understanding user behavior, catering to their needs, and directing them towards specific actions. The Action Funnel consists of four major steps:\n\n* **Establish the Objectives:** Before diving into the design, clearly define the goals you want to achieve through the website or app. Determine what actions you want the users to take (e.g., sign up, make a purchase, share content) and what constitutes a successful conversion.\n \n* **Understand User Mindsets:** Identify your target audience and recognize their needs, preferences, emotions, and pain points. Accomplishing this requires user research, creating personas, storyboarding, and empathy mapping, among other methods.\n \n* **Design the Optimal User Flow:** Craft a seamless and intuitive user journey by designing a clear path from the landing page to the desired action. Prioritize simplicity, usability, and efficiency. Make sure to include meaningful touchpoints and interactions to engage the users and make it easy for them to complete the intended action.\n \n* **Refine and Test the Experience:** Use wireframes and prototypes to test and iteratively refine the user experience. Employ user testing, A/B testing, and analytics to gather insight on user behavior, preferences, and engagement. Continuously use feedback to make improvements, ensuring that the design effectively leads visitors down the action funnel.\n \n\nBy implementing Stephen Wendell's `Create Action Funnel`, you can effectively guide users through an engaging journey that motivates them to become active customers, ultimately increasing conversion rates and overall satisfaction.",
|
||||
"description": "Stephen Wendel's CREATE Action Funnel is a behavioral design framework aimed at helping individuals or organizations encourage specific behaviors in others, especially in the context of product design. It breaks down the process of motivating action into six key stages. Each stage helps identify where users might drop off or face barriers, allowing designers or strategists to address these pain points effectively. These stages are:\n\n* **CUE:** The user must notice a cue or prompt that tells them to act. This could be a notification, a visual element, or any kind of reminder.\n \n* **REACTION:** The user must react positively to the cue. This stage involves emotional and cognitive processing, where the individual decides if the action is relevant or attractive.\n \n* **EVALUATION:** The user evaluates whether the action is worth their time, energy, or resources. They assess the benefits versus the effort required.\n \n* **ABILITY:** The user must feel capable of taking the action. This involves ensuring that the action is easy enough to do and aligns with their skills and resources.\n \n* **TIMING:** The action needs to happen at the right time. Users need to have the opportunity and be in the right context to act.\n \n* **EXECUTION:** Finally, the action must be carried out successfully. This is the stage where the behavior is completed.\n \n\nThe CREATE Action Funnel is helpful for product designers, marketers, or behavior change professionals, as it provides a structured way to understand user actions and design interventions to improve completion rates. It identifies and solves the gaps that occur between intention and action.",
|
||||
"links": [
|
||||
{
|
||||
"title": "Behavioral Science Crash Course: Steve Wendel's CREATE Action Funnel",
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"0Df110GZcDw5wbAe1eKoA": {
|
||||
"title": "Spectrum of Thinking Interventions",
|
||||
"description": "The _Spectrum of Thinking Interventions_ provides a structure to guide your UX design process, helping you identify the types and range of thinking interventions that the user may require. This spectrum encompasses four primary categories: guidance, explanation, exploration, and creation.\n\nGuidance\n--------\n\nGuidance-based interventions are designed to help users navigate through a digital product or service with minimal effort. They may be aimed at full-fledged beginners, casual users, or experts in their respective domains. Such interventions may include signposts, tooltips, and clearly articulated labels.\n\n_Examples:_\n\n* Visual cues (e.g., icons, colors)\n* Signposting (e.g., breadcrumbs)\n* In-context information (e.g., tool tips, hints)\n\nExplanation\n-----------\n\nExplanation-based interventions provide users with detailed narratives, overviews, or background information that helps them make informed decisions. This may include tutorials, articles, videos, or any other mediums that help explain complex concepts or instructions.\n\n_Examples:_\n\n* Multimedia tutorials\n* Articles or blog posts\n* Infographics or diagrams\n\nExploration\n-----------\n\nExploration-based interventions encourage users to understand and interact with the product by investigating, asking questions, or searching for solutions on their own. This can be done by providing interactive elements, multiple pathways, and opportunities for discovery.\n\n_Examples:_\n\n* Interactive simulations or models\n* Advanced search capabilities\n* Multiple UI paths for task completion\n\nCreation\n--------\n\nCreation-based interventions engage users by offering them the tools and resources to co-create or customize their experience. This type of intervention often involves a more extensive level of input and involvement from the user as they become active participants in the design process.\n\n_Examples:_\n\n* Customizable user interfaces\n* Allowing users to create their content\n* Enabling users to manage their preferences, settings, and configurations\n\nWith this spectrum in mind, it is essential as a UX designer to analyze which types of thinking interventions are most relevant to your target users and design the most accessible and effective solutions. Always consider how these interventions will influence users' decision-making processes and their overall satisfaction with your digital product or service.",
|
||||
"description": "The _Spectrum of Thinking Interventions_ provides a structure for understanding the different types of decision-making processes by illustrating how our minds would respond in a _default, lowest energy way_, if we didn't consciously do something different. This spectrum ranges from situations requiring minimal thought to those demanding intensive thinking, and includes the mechanisms (\"interventions\") that our minds will likely use.\n\n* **Habits:** Triggering a learned routine based on familiar cues\n* **Other intuitive responses:** Used in familiar or semi-familiar situations, with responses based on past experiences\n* **Active mindset or self-concept:** Used in ambiguous scenarios with multiple possible interpretations\n* **Heuristics:** Used in situations requiring conscious attention, but where decisions can be made more easily\n* **Focused, conscious calculation:** Used in unfamiliar scenarios or crucial decisions where deliberate focus is needed\n\nWith this spectrum in mind, it is essential as a UX designer to leverage on the mind's decision-making process, analyze which mechanisms are most applicable to your target users and design the most accessible and effective solutions.",
|
||||
"links": []
|
||||
},
|
||||
"kWA8CvocP1pkom2N7O4gb": {
|
||||
|
||||
@@ -242,13 +242,25 @@
|
||||
},
|
||||
"NCIzs3jbQTv1xXhAaGfZN": {
|
||||
"title": "v-text",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-text` directive is used to set the textContent property of an element. It's important to note that when using this directive it will overwrite the HTML content inside the element. The expected input is a string, so it's important to wrap any text in single quotes.\n\nExample:\n\n <template>\n <p v-text=\"'I am some text'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-text documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-text",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bZxtIBeIfeUcR32LZWrPW": {
|
||||
"title": "v-html",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-thml` directive is similar to the `v-text` directive, but the difference is that `v-html` renders its content as HTML. This means that if you pass an HTML element it will be rendered as an element and not plain text. Since the content is render as HTMl, it can pose a security risk if the content contains malicius JavaScript code. For this reason you should never use this directive in combination with user input, unless the input is first properly sanitized.\n\nExample:\n\n <template>\n <p v-html=\"'<h1>Text</h1>'\"></p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-html documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-html",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"_TlbGTKFCMO0wdLbC6xHX": {
|
||||
"title": "v-show",
|
||||
@@ -285,13 +297,25 @@
|
||||
},
|
||||
"a9caVhderJaVo0v14w8WB": {
|
||||
"title": "v-else-if",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "This directive is used to add additional conditions to a v-if and v-else block.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-else-if Documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-else-if",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3ftwRjQ9e1-qDT9BV53zr": {
|
||||
"title": "v-for",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-for` directive is used to render an HTML element, a block of elements, or even a component based on an array, an object, or a set number of times. When using this directive it is important to assign a unique key to each item to avoid issues and improve perfomance. This directive follows the `item in items` syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const foods = ref([\n {id: 1, name: \"apple\"},\n {id: 2, name: \"pear\"},\n {id: 3, name: \"pizza\"}\n ]);\n </script>\n \n <template>\n <p v-for=\"food in foods\" :key=\"food.id\">{{ food.name }}</p>\n </template>\n \n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-for documentation",
|
||||
"url": "https://vuejs.org/guide/essentials/list#v-for",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"hVuRmhXVP65IPtuHTORjJ": {
|
||||
"title": "v-on",
|
||||
@@ -300,12 +324,18 @@
|
||||
},
|
||||
"cuM9q9vYy8JpZPGeBffd1": {
|
||||
"title": "v-bind",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-bind` directive dynamically binds an HTML attribute to data.\n\nThe shorthand for this directive is `:`\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const image_url = ref(\"path/to/image.png\")\n </script>\n \n <template>\n <img :src=\"image_url\" />\n </template>\n \n\nVisit the following resources for more information:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-bind documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-bind",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cxu2Wbt306SxM4JKQQqnL": {
|
||||
"title": "v-model",
|
||||
"description": "The v-model directive in Vue.js is used for creating two-way data bindings on form input elements, such as , , and . This means that the data can be updated in the component when the user inputs something, and the UI will update if the data in the component changes.",
|
||||
"description": "The v-model directive in Vue.js is used for creating two-way data bindings on form input elements, such as `<input>`, `<textarea>`, and `<select>`. This means that the data can be updated in the component when the user inputs something, and the UI will update if the data in the component changes.",
|
||||
"links": [
|
||||
{
|
||||
"title": "Form Input Bindings",
|
||||
@@ -316,28 +346,58 @@
|
||||
},
|
||||
"m9pQ3daR3KiwRATcQysHA": {
|
||||
"title": "v-slot",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The v-slot directive to define slots in components, allowing you to pass and render content dynamically inside a component.\n\nFor named slots, you use v-slot with a specific slot name. This lets you pass different content to different parts of a component:\n\n <template>\n <custom-component>\n <template v-slot:header>\n <h1>Header Content</h1>\n </template>\n <template v-slot:footer>\n <p>Footer Content</p>\n </template>\n </custom-component>\n </template>\n \n\nThe shorthand for `v-slot` is `#`, for example `v-slot:header` becomes `#header`.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-slot documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-slot",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"5k9CrbzhNy9iiS6ez2UE6": {
|
||||
"title": "v-once",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-once` directive makes an HTML element render only once, skipping every future update.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const input = ref(\"Some Text\");\n </script>\n \n <template>\n <input v-model=\"input\">\n <p v-once>{{ input }}</p>\n </template>\n \n\nIn this example the **p** element will not change its text even if the input variable is changed through the **input** element.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-once documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-once",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mlsrhioiEkqnRIL6O3hNa": {
|
||||
"title": "v-pre",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The `v-pre` directive makes an element render its content as-is, skipping its compilation. The most common use case is when displaying raw mustache syntax.\n\nExample:\n\n <script setup>\n import { ref } from 'vue';\n const text = ref(\"Some Text\")\n </script>\n \n <template>\n <p v-pre >{{ text }}</p>\n </template>\n \n\nThe **p** element will display: `{{ text }}` and not `Some Text` because the compilation is skipped.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-pre Documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-pre",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RrSekP8Ub01coegMwLP6a": {
|
||||
"title": "v-cloak",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "The v-cloak directive is used to prevent the uncompiled Vue template from being visible while the Vue instance is still loading. It temporarily hides the content until Vue has finished compiling the template\n\nThe v-cloak directive remains until the component instance is mounted.\n\n <div v-cloak>\n {{ message }}\n </div>\n \n\nCombined with CSS, you can hide elements with v-cloak until they are ready.\n\n [v-cloak] {\n display: none;\n }\n \n\nThe `<div>` will not be visible until the compilation is done.\n\nVisit the following resources to learn more:",
|
||||
"links": [
|
||||
{
|
||||
"title": "v-cloak documentation",
|
||||
"url": "https://vuejs.org/api/built-in-directives.html#v-cloak",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"RRPhAxIqvAcjZIcLe_N8-": {
|
||||
"title": "Optimizing Renders",
|
||||
"description": "",
|
||||
"links": []
|
||||
"description": "Optimizing rendering is crucial for ensuring a smooth and efficient user experience across all your frontend projects. Sluggish webpages can lead to frustration for users, and potentially cause them to entirely abandon your web application. This issue comes up most often in single-page applications (SPAs), where the entirety of your application is loaded within a single webpage, and updates to it are handled dynamically without needing a full reload of the webpage.\n\nLearn more from the following resources:",
|
||||
"links": [
|
||||
{
|
||||
"title": "Optimizing rendering in Vue",
|
||||
"url": "https://blog.logrocket.com/optimizing-rendering-vue/",
|
||||
"type": "article"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dxwKfBxd5KYVkfEPMdHp-": {
|
||||
"title": "Debugging",
|
||||
|
||||
BIN
public/roadmaps/redis.png
Normal file
BIN
public/roadmaps/redis.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
@@ -71,6 +71,7 @@ Here is the list of available roadmaps with more being actively worked upon.
|
||||
- [Design System Roadmap](https://roadmap.sh/design-system)
|
||||
- [PostgreSQL Roadmap](https://roadmap.sh/postgresql-dba)
|
||||
- [SQL Roadmap](https://roadmap.sh/sql)
|
||||
- [Redis Roadmap](https://roadmap.sh/redis)
|
||||
- [Blockchain Roadmap](https://roadmap.sh/blockchain)
|
||||
- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core)
|
||||
- [System Design Roadmap](https://roadmap.sh/system-design)
|
||||
|
||||
@@ -12,8 +12,8 @@ const ALL_BEST_PRACTICE_DIR = path.join(
|
||||
process.cwd(),
|
||||
'/src/data/best-practices',
|
||||
);
|
||||
const ALL_GUIDE_DIR = path.join(process.cwd(), '/src/data/guides');
|
||||
const ALl_AUTHOR_DIR = path.join(process.cwd(), '/src/data/authors');
|
||||
const ALL_GUIDE_DIR = path.join(process.cwd(), '/src/content/guides');
|
||||
const ALl_AUTHOR_DIR = path.join(process.cwd(), '/src/content/authors');
|
||||
const ALL_ROADMAP_IMAGE_DIR = path.join(process.cwd(), '/public/roadmaps');
|
||||
const ALL_BEST_PRACTICE_IMAGE_DIR = path.join(
|
||||
process.cwd(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import type { FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useId, useState } from 'react';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
|
||||
|
||||
@@ -53,13 +53,16 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
setError(error?.message || 'Something went wrong. Please try again later.');
|
||||
};
|
||||
|
||||
const emailFieldId = `form:${useId()}`;
|
||||
const passwordFieldId = `form:${useId()}`;
|
||||
|
||||
return (
|
||||
<form className="w-full" onSubmit={handleFormSubmit}>
|
||||
<label htmlFor="email" className="sr-only">
|
||||
<label htmlFor={emailFieldId} className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
id={emailFieldId}
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
@@ -69,11 +72,11 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
|
||||
value={email}
|
||||
onInput={(e) => setEmail(String((e.target as any).value))}
|
||||
/>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
<label htmlFor={passwordFieldId} className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
id={passwordFieldId}
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
|
||||
39
src/components/Changelog/ChangelogItem.astro
Normal file
39
src/components/Changelog/ChangelogItem.astro
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import type { ChangelogFileType } from '../../lib/changelog';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
interface Props {
|
||||
changelog: ChangelogFileType;
|
||||
}
|
||||
|
||||
const { changelog } = Astro.props;
|
||||
const { data: frontmatter } = changelog;
|
||||
const { Content } = await changelog.render();
|
||||
|
||||
const formattedDate = DateTime.fromJSDate(frontmatter.date).toFormat(
|
||||
'dd LLL, yyyy',
|
||||
);
|
||||
---
|
||||
|
||||
<div class='relative'>
|
||||
<span
|
||||
class='absolute -left-6 top-2 h-2 w-2 flex-shrink-0 rounded-full bg-gray-300'
|
||||
></span>
|
||||
|
||||
<div class='mb-3 flex items-center gap-2'>
|
||||
<span class='flex-shrink-0 text-xs tracking-wide text-gray-400'>
|
||||
{formattedDate}
|
||||
</span>
|
||||
<span class='truncate text-base font-medium'>
|
||||
{frontmatter.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class='rounded-xl border bg-white p-6'>
|
||||
<div
|
||||
class='prose prose-sm prose-h2:mt-3 prose-h2:text-lg prose-h2:font-medium prose-p:mb-0 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-ul:my-0 prose-ul:rounded-lg prose-ul:bg-gray-100 prose-ul:px-4 prose-ul:py-4 prose-ul:pl-7 prose-img:mt-0 prose-img:rounded-lg [&>blockquote>p]:mt-0 [&>ul>li]:my-0 [&>ul>li]:mb-1 [&>ul]:mt-3'
|
||||
>
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -9,28 +9,31 @@ export function ContentConfirmationModal(props: ContentConfirmationModalProps) {
|
||||
const { onClose, onClick } = props;
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose}>
|
||||
<Modal onClose={onClose} wrapperClassName="max-w-lg">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-semibold">Roadmap Content</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
Copy Node Details and Resources?
|
||||
</h2>
|
||||
<p className="balanc text-gray-600">
|
||||
Do you want to copy the content of this roadmap?
|
||||
This will just copy the roadmap in your team. Would you like to copy
|
||||
the resource links and node details as well?
|
||||
</p>
|
||||
<div className="mt-4 grid grid-cols-2 gap-2">
|
||||
<button
|
||||
className="rounded-lg border p-2.5 font-medium"
|
||||
className="rounded-lg border p-2.5 font-normal"
|
||||
onClick={() => {
|
||||
onClick(false);
|
||||
}}
|
||||
>
|
||||
No
|
||||
No, copy roadmap only
|
||||
</button>
|
||||
<button
|
||||
className="rounded-lg border bg-black p-2.5 font-medium text-white hover:opacity-80"
|
||||
className="rounded-lg border bg-black p-2.5 font-normal text-white hover:opacity-80"
|
||||
onClick={() => {
|
||||
onClick(true);
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
Yes, also copy resources
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { showLoginPopup } from '../../lib/popup.ts';
|
||||
import { isLoggedIn } from '../../lib/jwt.ts';
|
||||
import { useState } from 'react';
|
||||
import { CreateRoadmapModal } from './CreateRoadmap/CreateRoadmapModal.tsx';
|
||||
import { RoadmapAlert } from '../RoadmapAlert.tsx';
|
||||
|
||||
export function CustomRoadmapAlert() {
|
||||
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
|
||||
@@ -23,33 +24,18 @@ export function CustomRoadmapAlert() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="relative mb-5 mt-0 rounded-md border border-yellow-500 bg-yellow-100 p-2 sm:-mt-6 sm:mb-7 sm:p-2.5">
|
||||
<p className="mb-2.5 mt-2 text-sm text-yellow-800 sm:mb-1.5 sm:mt-1 sm:text-base">
|
||||
This is a custom roadmap made by a community member and is not
|
||||
verified by <span className="font-semibold">roadmap.sh</span>
|
||||
</p>
|
||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||
<a
|
||||
href="/roadmaps"
|
||||
className="inline-flex items-center gap-1.5 text-sm font-semibold text-yellow-700 underline-offset-2 hover:underline"
|
||||
>
|
||||
<BadgeCheck className="h-4 w-4 stroke-[2.5]" />
|
||||
Visit Official Roadmaps
|
||||
</a>
|
||||
<span className="hidden font-black text-yellow-700 sm:block">
|
||||
·
|
||||
</span>
|
||||
<a
|
||||
href="/community"
|
||||
className="inline-flex items-center gap-1.5 text-sm font-semibold text-yellow-700 underline-offset-2 hover:underline"
|
||||
>
|
||||
<HeartHandshake className="h-4 w-4 stroke-[2.5]" />
|
||||
More Community Roadmaps
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<MessageCircleHeart className="absolute bottom-2 right-2 hidden h-12 w-12 text-yellow-500 opacity-50 sm:block" />
|
||||
</div>
|
||||
<RoadmapAlert
|
||||
title="Community Roadmaps"
|
||||
description={
|
||||
<>
|
||||
This is a custom roadmap made by a community member and is not
|
||||
verified by <span className="font-semibold">roadmap.sh</span>
|
||||
</>
|
||||
}
|
||||
floatingIcon={MessageCircleHeart}
|
||||
className="mb-5 mt-0 sm:-mt-6 sm:mb-7"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,8 @@ export function FlowRoadmapRenderer(props: FlowRoadmapRendererProps) {
|
||||
{hideRenderer && (
|
||||
<EmptyRoadmap
|
||||
roadmapId={roadmapId}
|
||||
canManage={roadmap.canManage}
|
||||
// @ts-ignore
|
||||
canManage={roadmap?.canManage}
|
||||
className="grow"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,13 @@ import { DashboardCardLink } from './DashboardCardLink';
|
||||
import { useState } from 'react';
|
||||
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
import { Bot, BrainCircuit, Map, PencilRuler } from 'lucide-react';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Bot,
|
||||
BrainCircuit,
|
||||
Map,
|
||||
PencilRuler,
|
||||
} from 'lucide-react';
|
||||
|
||||
type DashboardAiRoadmapsProps = {
|
||||
roadmaps: {
|
||||
@@ -20,9 +26,18 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
|
||||
AI Generated Roadmaps
|
||||
</h2>
|
||||
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
|
||||
<h2 className="text-xs uppercase text-gray-400">
|
||||
My AI Roadmaps
|
||||
</h2>
|
||||
|
||||
<a
|
||||
href="/ai/explore"
|
||||
className="rounded-full bg-gray-200 px-2.5 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
|
||||
>
|
||||
AI Generated Roadmaps
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{!isLoading && roadmaps.length === 0 && (
|
||||
<DashboardCardLink
|
||||
@@ -48,7 +63,7 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
|
||||
{roadmaps.map((roadmap) => (
|
||||
<a
|
||||
href={`/ai/${roadmap.slug}`}
|
||||
className="relative rounded-md border bg-white p-2.5 text-left text-sm shadow-sm truncate hover:border-gray-400 hover:bg-gray-50"
|
||||
className="relative truncate rounded-md border bg-white p-2.5 text-left text-sm shadow-sm hover:border-gray-400 hover:bg-gray-50"
|
||||
>
|
||||
{roadmap.title}
|
||||
</a>
|
||||
@@ -69,9 +84,7 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) {
|
||||
|
||||
type CustomProgressCardSkeletonProps = {};
|
||||
|
||||
function RoadmapCardSkeleton(
|
||||
props: CustomProgressCardSkeletonProps,
|
||||
) {
|
||||
function RoadmapCardSkeleton(props: CustomProgressCardSkeletonProps) {
|
||||
return (
|
||||
<div className="h-[42px] w-full animate-pulse rounded-md bg-gray-200" />
|
||||
);
|
||||
|
||||
@@ -19,7 +19,6 @@ export function DashboardCardLink(props: DashboardCardLinkProps) {
|
||||
className,
|
||||
)}
|
||||
href={href}
|
||||
target="_blank"
|
||||
>
|
||||
<Icon className="mb-4 size-10 text-gray-300" strokeWidth={1.25} />
|
||||
<h4 className="text-xl font-semibold tracking-wide">{title}</h4>
|
||||
|
||||
@@ -54,13 +54,14 @@ export function DashboardPage(props: DashboardPageProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
|
||||
<div className="container">
|
||||
<div className="mb-6 sm:mb-8 flex flex-wrap items-center gap-1.5">
|
||||
<div className="mb-6 flex flex-wrap items-center gap-1.5 sm:mb-8">
|
||||
<DashboardTab
|
||||
label="Personal"
|
||||
isActive={!selectedTeamId}
|
||||
onClick={() => setSelectedTeamId(undefined)}
|
||||
avatar={userAvatar}
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
<>
|
||||
<DashboardTabSkeleton />
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { cn } from '../../lib/classname';
|
||||
|
||||
type EmptyStackMessageProps = {
|
||||
number: number;
|
||||
number: number | string;
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
buttonLink: string;
|
||||
bodyClassName?: string;
|
||||
};
|
||||
|
||||
export function EmptyStackMessage(props: EmptyStackMessageProps) {
|
||||
const { number, title, description, buttonText, buttonLink } = props;
|
||||
const { number, title, description, buttonText, buttonLink, bodyClassName } =
|
||||
props;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-black/50">
|
||||
<div className="flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm">
|
||||
<div
|
||||
className={cn(
|
||||
'flex max-w-[200px] flex-col items-center justify-center rounded-md bg-white p-4 shadow-sm',
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300 text-white">
|
||||
{number}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,13 @@ import { DashboardCardLink } from './DashboardCardLink';
|
||||
import { useState } from 'react';
|
||||
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
import {Bot, BrainCircuit, Map, PencilRuler} from 'lucide-react';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
Bot,
|
||||
BrainCircuit,
|
||||
Map,
|
||||
PencilRuler,
|
||||
} from 'lucide-react';
|
||||
|
||||
type ListDashboardCustomProgressProps = {
|
||||
progresses: UserProgress[];
|
||||
@@ -40,9 +46,18 @@ export function ListDashboardCustomProgress(
|
||||
<>
|
||||
{customRoadmapModal}
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs uppercase text-gray-400">
|
||||
{isAIGeneratedRoadmaps ? 'AI Generated Roadmaps' : 'Custom Roadmaps'}
|
||||
</h2>
|
||||
<div className="mb-2 mt-6 flex items-center justify-between gap-2">
|
||||
<h2 className="text-xs uppercase text-gray-400">
|
||||
{isAIGeneratedRoadmaps ? 'My AI Roadmaps' : 'My Custom Roadmaps'}
|
||||
</h2>
|
||||
|
||||
<a
|
||||
href="/community"
|
||||
className="rounded-full bg-gray-200 px-2.5 py-0.5 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
|
||||
>
|
||||
Community Roadmaps
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{!isLoading && progresses.length === 0 && isAIGeneratedRoadmaps && (
|
||||
<DashboardCardLink
|
||||
|
||||
@@ -14,6 +14,9 @@ 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';
|
||||
|
||||
type UserDashboardResponse = {
|
||||
name: string;
|
||||
@@ -21,6 +24,7 @@ type UserDashboardResponse = {
|
||||
avatar: string;
|
||||
headline: string;
|
||||
username: string;
|
||||
profileVisibility: AllowedProfileVisibility;
|
||||
progresses: UserProgress[];
|
||||
projects: ProjectStatusDocument[];
|
||||
aiRoadmaps: {
|
||||
@@ -222,20 +226,22 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
return 0;
|
||||
});
|
||||
|
||||
const { username } = personalDashboardDetails || {};
|
||||
|
||||
return (
|
||||
<section>
|
||||
{isLoading ? (
|
||||
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
|
||||
) : (
|
||||
<div className="flex items-start sm:items-center justify-between flex-col sm:flex-row gap-1">
|
||||
<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="text-xs text-purple-600 underline underline-offset-2 hover:text-purple-700"
|
||||
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"
|
||||
>
|
||||
Looking for old homepage? Click here
|
||||
Visit Homepage
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
@@ -253,8 +259,20 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
<DashboardCard
|
||||
imgUrl={avatarLink}
|
||||
title={name!}
|
||||
description="Setup your profile"
|
||||
href="/account/update-profile"
|
||||
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
|
||||
@@ -273,7 +291,7 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
|
||||
<DashboardCard
|
||||
icon={CheckEmoji}
|
||||
title="Best Practices"
|
||||
description="Do things right way"
|
||||
description="Do things the right way"
|
||||
href="/best-practices"
|
||||
/>
|
||||
</>
|
||||
@@ -312,34 +330,61 @@ type DashboardCardProps = {
|
||||
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 } = props;
|
||||
const {
|
||||
icon: Icon,
|
||||
imgUrl,
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
externalLinkHref,
|
||||
externalLinkIcon: ExternalLinkIcon,
|
||||
externalLinkText,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
className="flex flex-col overflow-hidden rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50"
|
||||
<div
|
||||
className={cn(
|
||||
'relative overflow-hidden',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{Icon && (
|
||||
<div className="px-4 pb-3 pt-4">
|
||||
<Icon className="size-6" />
|
||||
</div>
|
||||
)}
|
||||
<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>
|
||||
)}
|
||||
|
||||
{imgUrl && (
|
||||
<div className="px-4 pb-1.5 pt-3.5">
|
||||
<img src={imgUrl} alt={title} className="size-8 rounded-full" />
|
||||
</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="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>
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ type ProgressStackProps = {
|
||||
topicDoneToday: number;
|
||||
};
|
||||
|
||||
const MAX_PROGRESS_TO_SHOW = 5;
|
||||
const MAX_BOOKMARKS_TO_SHOW = 5;
|
||||
const MAX_PROGRESS_TO_SHOW = 11;
|
||||
const MAX_PROJECTS_TO_SHOW = 8;
|
||||
|
||||
type ProgressLaneProps = {
|
||||
@@ -36,6 +35,7 @@ type ProgressLaneProps = {
|
||||
linkHref?: string;
|
||||
isLoading?: boolean;
|
||||
isEmpty?: boolean;
|
||||
loadingWrapperClassName?: string;
|
||||
loadingSkeletonCount?: number;
|
||||
loadingSkeletonClassName?: string;
|
||||
children: React.ReactNode;
|
||||
@@ -43,6 +43,7 @@ type ProgressLaneProps = {
|
||||
emptyIcon?: LucideIcon;
|
||||
emptyLinkText?: string;
|
||||
emptyLinkHref?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function ProgressLane(props: ProgressLaneProps) {
|
||||
@@ -51,6 +52,7 @@ function ProgressLane(props: ProgressLaneProps) {
|
||||
linkText,
|
||||
linkHref,
|
||||
isLoading = false,
|
||||
loadingWrapperClassName = '',
|
||||
loadingSkeletonCount = 4,
|
||||
loadingSkeletonClassName = '',
|
||||
children,
|
||||
@@ -59,10 +61,16 @@ function ProgressLane(props: ProgressLaneProps) {
|
||||
emptyMessage = `No ${title.toLowerCase()} to show`,
|
||||
emptyLinkHref = '/roadmaps',
|
||||
emptyLinkText = 'Explore',
|
||||
className,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm">
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{isLoading && (
|
||||
<div className={'flex flex-row justify-between'}>
|
||||
<div className="h-[16px] w-[75px] animate-pulse rounded-md bg-gray-100"></div>
|
||||
@@ -86,11 +94,13 @@ function ProgressLane(props: ProgressLaneProps) {
|
||||
|
||||
<div className="mt-4 flex flex-grow flex-col gap-1.5">
|
||||
{isLoading && (
|
||||
<>
|
||||
<div
|
||||
className={cn('grid grid-cols-2 gap-2', loadingWrapperClassName)}
|
||||
>
|
||||
{Array.from({ length: loadingSkeletonCount }).map((_, index) => (
|
||||
<CardSkeleton key={index} className={loadingSkeletonClassName} />
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && children}
|
||||
|
||||
@@ -119,29 +129,27 @@ export function ProgressStack(props: ProgressStackProps) {
|
||||
const { progresses, projects, isLoading, accountStreak, topicDoneToday } =
|
||||
props;
|
||||
|
||||
const bookmarkedProgresses = progresses.filter(
|
||||
(progress) => progress?.isFavorite,
|
||||
);
|
||||
|
||||
const userProgresses = progresses.filter(
|
||||
(progress) => !progress?.isFavorite || progress?.done > 0,
|
||||
);
|
||||
|
||||
const [showAllProgresses, setShowAllProgresses] = useState(false);
|
||||
const sortedProgresses = progresses.sort((a, b) => {
|
||||
if (a.isFavorite && !b.isFavorite) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!a.isFavorite && b.isFavorite) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
const userProgressesToShow = showAllProgresses
|
||||
? userProgresses
|
||||
: userProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
|
||||
? sortedProgresses
|
||||
: sortedProgresses.slice(0, MAX_PROGRESS_TO_SHOW);
|
||||
|
||||
const [showAllProjects, setShowAllProjects] = useState(false);
|
||||
const projectsToShow = showAllProjects
|
||||
? projects
|
||||
: projects.slice(0, MAX_PROJECTS_TO_SHOW);
|
||||
|
||||
const [showAllBookmarks, setShowAllBookmarks] = useState(false);
|
||||
const bookmarksToShow = showAllBookmarks
|
||||
? bookmarkedProgresses
|
||||
: bookmarkedProgresses.slice(0, MAX_BOOKMARKS_TO_SHOW);
|
||||
|
||||
const totalProjectFinished = projects.filter(
|
||||
(project) => project.repositoryUrl,
|
||||
).length;
|
||||
@@ -167,92 +175,70 @@ export function ProgressStack(props: ProgressStackProps) {
|
||||
</div>
|
||||
|
||||
<div className="mt-2 grid min-h-[330px] grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
<div className="relative">
|
||||
{!isLoading && bookmarksToShow.length === 0 && (
|
||||
<div className="relative col-span-2">
|
||||
{!isLoading && userProgressesToShow.length === 0 && (
|
||||
<EmptyStackMessage
|
||||
number={1}
|
||||
title={'Bookmark Roadmaps'}
|
||||
description={'Bookmark some roadmaps to access them quickly'}
|
||||
title={'Bookmark some Roadmaps'}
|
||||
description={
|
||||
'Bookmark some roadmaps to access them quickly and start updating your progress'
|
||||
}
|
||||
buttonText={'Explore Roadmaps'}
|
||||
buttonLink={'/roadmaps'}
|
||||
bodyClassName="max-w-[280px]"
|
||||
/>
|
||||
)}
|
||||
|
||||
<ProgressLane
|
||||
title={'Bookmarks'}
|
||||
title="Progress & Bookmarks"
|
||||
isLoading={isLoading}
|
||||
loadingSkeletonCount={5}
|
||||
linkHref={'/roadmaps'}
|
||||
linkText={'Roadmaps'}
|
||||
isEmpty={bookmarksToShow.length === 0}
|
||||
loadingSkeletonCount={MAX_PROGRESS_TO_SHOW}
|
||||
linkHref="/roadmaps"
|
||||
linkText="Roadmaps"
|
||||
isEmpty={userProgressesToShow.length === 0}
|
||||
emptyIcon={Bookmark}
|
||||
emptyMessage={'No bookmarks to show'}
|
||||
emptyLinkHref={'/roadmaps'}
|
||||
emptyLinkText={'Explore Roadmaps'}
|
||||
>
|
||||
{bookmarksToShow.map((progress) => {
|
||||
return (
|
||||
<DashboardBookmarkCard
|
||||
key={progress.resourceId}
|
||||
bookmark={progress}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{userProgressesToShow.length > 0 && (
|
||||
<>
|
||||
{userProgressesToShow.map((progress) => {
|
||||
const isFavorite =
|
||||
progress.isFavorite &&
|
||||
!progress.done &&
|
||||
!progress.skipped;
|
||||
|
||||
if (isFavorite) {
|
||||
return (
|
||||
<DashboardBookmarkCard
|
||||
key={progress.resourceId}
|
||||
bookmark={progress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardProgressCard
|
||||
key={progress.resourceId}
|
||||
progress={progress}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{sortedProgresses.length > MAX_PROGRESS_TO_SHOW && (
|
||||
<ShowAllButton
|
||||
showAll={showAllProgresses}
|
||||
setShowAll={setShowAllProgresses}
|
||||
count={sortedProgresses.length}
|
||||
maxCount={MAX_PROGRESS_TO_SHOW}
|
||||
className="min-h-[38px] rounded-md border border-dashed leading-none"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{bookmarkedProgresses.length > MAX_BOOKMARKS_TO_SHOW && (
|
||||
<ShowAllButton
|
||||
showAll={showAllBookmarks}
|
||||
setShowAll={setShowAllBookmarks}
|
||||
count={bookmarkedProgresses.length}
|
||||
maxCount={MAX_BOOKMARKS_TO_SHOW}
|
||||
className="mb-0.5 mt-3"
|
||||
/>
|
||||
)}
|
||||
</ProgressLane>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
{!isLoading && userProgressesToShow.length === 0 && (
|
||||
<EmptyStackMessage
|
||||
number={2}
|
||||
title={'Track Progress'}
|
||||
description={'Pick your first roadmap and start learning'}
|
||||
buttonText={'Explore roadmaps'}
|
||||
buttonLink={'/roadmaps'}
|
||||
/>
|
||||
)}
|
||||
<ProgressLane
|
||||
title={'Progress'}
|
||||
linkHref={'/roadmaps'}
|
||||
linkText={'Roadmaps'}
|
||||
isLoading={isLoading}
|
||||
loadingSkeletonCount={5}
|
||||
isEmpty={userProgressesToShow.length === 0}
|
||||
emptyMessage={'Update your Progress'}
|
||||
emptyIcon={Map}
|
||||
emptyLinkText={'Explore Roadmaps'}
|
||||
>
|
||||
{userProgressesToShow.length > 0 && (
|
||||
<>
|
||||
{userProgressesToShow.map((progress) => {
|
||||
return (
|
||||
<DashboardProgressCard
|
||||
key={progress.resourceId}
|
||||
progress={progress}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{userProgresses.length > MAX_PROGRESS_TO_SHOW && (
|
||||
<ShowAllButton
|
||||
showAll={showAllProgresses}
|
||||
setShowAll={setShowAllProgresses}
|
||||
count={userProgresses.length}
|
||||
maxCount={MAX_PROGRESS_TO_SHOW}
|
||||
className="mb-0.5 mt-3"
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</ProgressLane>
|
||||
</div>
|
||||
|
||||
@@ -262,6 +248,7 @@ export function ProgressStack(props: ProgressStackProps) {
|
||||
linkHref={'/projects'}
|
||||
linkText={'Projects'}
|
||||
isLoading={isLoading}
|
||||
loadingWrapperClassName="grid-cols-1"
|
||||
loadingSkeletonClassName={'h-5'}
|
||||
loadingSkeletonCount={8}
|
||||
isEmpty={projectsToShow.length === 0}
|
||||
@@ -272,7 +259,7 @@ export function ProgressStack(props: ProgressStackProps) {
|
||||
>
|
||||
{!isLoading && projectsToShow.length === 0 && (
|
||||
<EmptyStackMessage
|
||||
number={3}
|
||||
number={2}
|
||||
title={'Build your first project'}
|
||||
description={'Pick a project to practice and start building'}
|
||||
buttonText={'Explore Projects'}
|
||||
@@ -317,17 +304,15 @@ function ShowAllButton(props: ShowAllButtonProps) {
|
||||
const { showAll, setShowAll, count, maxCount, className } = props;
|
||||
|
||||
return (
|
||||
<span className="flex flex-grow items-end">
|
||||
<button
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700',
|
||||
className,
|
||||
)}
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
>
|
||||
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>}
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700',
|
||||
className,
|
||||
)}
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
>
|
||||
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -341,7 +326,7 @@ function CardSkeleton(props: CardSkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'h-10 w-full animate-pulse rounded-md bg-gray-100',
|
||||
'h-[38px] w-full animate-pulse rounded-md bg-gray-100',
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import type { GuideFileType } from '../lib/guide';
|
||||
import GuideListItem from './GuideListItem.astro';
|
||||
import { QuestionGroupType } from '../lib/question-group';
|
||||
import type { QuestionGroupType } from '../lib/question-group';
|
||||
|
||||
export interface Props {
|
||||
heading: string;
|
||||
@@ -15,8 +15,8 @@ const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
|
||||
...guides,
|
||||
...questions,
|
||||
].sort((a, b) => {
|
||||
const aDate = new Date(a.frontmatter.date);
|
||||
const bDate = new Date(b.frontmatter.date);
|
||||
const aDate = new Date(a.data.date!);
|
||||
const bDate = new Date(b.data.date!);
|
||||
|
||||
return bDate.getTime() - aDate.getTime();
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@ export function ProgressNudge(props: ProgressNudgeProps) {
|
||||
const $totalRoadmapNodes = useStore(totalRoadmapNodes);
|
||||
const $roadmapProgress = useStore(roadmapProgress);
|
||||
|
||||
const done = $roadmapProgress?.done?.length || 0;
|
||||
const done =
|
||||
($roadmapProgress?.done?.length || 0) +
|
||||
($roadmapProgress?.skipped?.length || 0);
|
||||
|
||||
const hasProgress = done > 0;
|
||||
|
||||
@@ -51,7 +53,8 @@ export function ProgressNudge(props: ProgressNudgeProps) {
|
||||
<span className="relative -top-[0.45px] mr-2 text-xs font-medium uppercase text-yellow-400">
|
||||
Progress
|
||||
</span>
|
||||
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of <span>{$totalRoadmapNodes}</span> Done
|
||||
<span>{done > $totalRoadmapNodes ? $totalRoadmapNodes : done}</span> of{' '}
|
||||
<span>{$totalRoadmapNodes}</span> Done
|
||||
</span>
|
||||
|
||||
<span
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BadgeCheck, Telescope, Wand } from 'lucide-react';
|
||||
import { BadgeCheck, Bot, Telescope, Wand } from 'lucide-react';
|
||||
import { RoadmapAlert } from '../RoadmapAlert';
|
||||
|
||||
type AIRoadmapAlertProps = {
|
||||
isListing?: boolean;
|
||||
@@ -8,46 +9,20 @@ export function AIRoadmapAlert(props: AIRoadmapAlertProps) {
|
||||
const { isListing = false } = props;
|
||||
|
||||
return (
|
||||
<div className="mb-3 w-full rounded-xl bg-yellow-100 px-4 py-3 text-yellow-800">
|
||||
<h2 className="flex items-center text-base font-semibold text-yellow-800 sm:text-lg">
|
||||
AI Generated Roadmap{isListing ? 's' : ''}{' '}
|
||||
<span className="ml-1.5 rounded-md border border-yellow-500 bg-yellow-200 px-1.5 text-xs uppercase tracking-wide text-yellow-800">
|
||||
Beta
|
||||
</span>
|
||||
</h2>
|
||||
<p className="mb-2 mt-1">
|
||||
{isListing
|
||||
? 'These are AI generated roadmaps and are not verified by'
|
||||
: 'This is an AI generated roadmap and is not verified by'}{' '}
|
||||
<span className={'font-semibold'}>roadmap.sh</span>. We are currently in
|
||||
beta and working hard to improve the quality of the generated roadmaps.
|
||||
</p>
|
||||
<p className="mb-1.5 mt-2 flex flex-col gap-2 text-sm sm:flex-row">
|
||||
{isListing ? (
|
||||
<a
|
||||
href="/ai"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
|
||||
>
|
||||
<Wand size={15} />
|
||||
Create your own Roadmap with AI
|
||||
</a>
|
||||
) : (
|
||||
<a
|
||||
href="/ai/explore"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
|
||||
>
|
||||
<Telescope size={15} />
|
||||
Explore other AI Roadmaps
|
||||
</a>
|
||||
)}
|
||||
<a
|
||||
href="/roadmaps"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 bg-yellow-200 px-2 py-1 text-yellow-800 transition-colors hover:bg-yellow-300"
|
||||
>
|
||||
<BadgeCheck size={15} />
|
||||
Visit Official Roadmaps
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<RoadmapAlert
|
||||
title={`AI Generated Roadmap${isListing ? 's' : ''}`}
|
||||
badgeText="Beta"
|
||||
description={
|
||||
<>
|
||||
{isListing
|
||||
? 'These are AI generated roadmaps and are not verified by'
|
||||
: 'This is an AI generated roadmap and is not verified by'}{' '}
|
||||
<span className={'font-semibold'}>roadmap.sh</span>. We are currently
|
||||
in beta and working hard to improve the quality of the generated
|
||||
roadmaps.
|
||||
</>
|
||||
}
|
||||
floatingIcon={Bot}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ interface Props {
|
||||
|
||||
const { guide } = Astro.props;
|
||||
|
||||
const allHeadings = guide.getHeadings();
|
||||
const { headings: allHeadings, Content } = await guide.render();
|
||||
const tableOfContent = getGuideTableOfContent(allHeadings);
|
||||
|
||||
const showTableOfContent = tableOfContent.length > 0;
|
||||
const { frontmatter: guideFrontmatter, author } = guide;
|
||||
const { data: guideFrontmatter, author } = guide;
|
||||
---
|
||||
|
||||
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
|
||||
@@ -40,26 +40,26 @@ const { frontmatter: guideFrontmatter, author } = guide;
|
||||
</h1>
|
||||
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
|
||||
<a
|
||||
href={`/authors/${author.id}`}
|
||||
href={`/authors/${author.slug}`}
|
||||
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
alt={author.data.name}
|
||||
src={author.data.imageUrl}
|
||||
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
{author.data.name}
|
||||
</a>
|
||||
<span class='mx-2 hidden sm:inline'>·</span>
|
||||
<a
|
||||
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.slug}.md`}
|
||||
target='_blank'
|
||||
>
|
||||
Improve this Guide
|
||||
</a>
|
||||
</p>
|
||||
<guide.Content />
|
||||
<Content />
|
||||
</MarkdownFile>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { guide } = Astro.props;
|
||||
const { frontmatter, author } = guide;
|
||||
const { data: frontmatter, author } = guide;
|
||||
|
||||
return undefined;
|
||||
---
|
||||
@@ -18,18 +18,18 @@ return undefined;
|
||||
class='hidden items-center justify-start text-gray-400 sm:flex sm:justify-center'
|
||||
>
|
||||
{
|
||||
author?.frontmatter && (
|
||||
author?.data && (
|
||||
<>
|
||||
<a
|
||||
href={`/authors/${author.id}`}
|
||||
href={`/authors/${author.slug}`}
|
||||
class='inline-flex items-center font-medium hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
alt={author.data.name}
|
||||
src={author.data.imageUrl}
|
||||
class='mr-2 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
{author.data.name}
|
||||
</a>
|
||||
<span class='mx-1.5'>·</span>
|
||||
</>
|
||||
@@ -39,7 +39,7 @@ return undefined;
|
||||
<span class='mx-1.5'>·</span>
|
||||
<a
|
||||
class='text-blue-400 hover:text-blue-500 hover:underline'
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.id}.md`}
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/guides/${guide.slug}.md`}
|
||||
target='_blank'>Improve this Guide</a
|
||||
>
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import type { GuideFileType, GuideFrontmatter } from '../lib/guide';
|
||||
import type { GuideFileType } from '../lib/guide';
|
||||
import { replaceVariables } from '../lib/markdown';
|
||||
import { QuestionGroupType } from '../lib/question-group';
|
||||
import type { QuestionGroupType } from '../lib/question-group';
|
||||
|
||||
export interface Props {
|
||||
guide: GuideFileType | QuestionGroupType;
|
||||
@@ -14,7 +14,7 @@ function isQuestionGroupType(
|
||||
}
|
||||
|
||||
const { guide } = Astro.props;
|
||||
const { frontmatter, id } = guide;
|
||||
const { data: frontmatter, slug: id } = guide;
|
||||
|
||||
let pageUrl = '';
|
||||
let guideType = '';
|
||||
@@ -23,9 +23,9 @@ if (isQuestionGroupType(guide)) {
|
||||
pageUrl = `/questions/${id}`;
|
||||
guideType = 'Questions';
|
||||
} else {
|
||||
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
|
||||
const excludedBySlug = (frontmatter as GuideFileType['data']).excludedBySlug;
|
||||
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
|
||||
guideType = (frontmatter as GuideFrontmatter).type;
|
||||
guideType = (frontmatter as GuideFileType['data']).type;
|
||||
}
|
||||
---
|
||||
|
||||
@@ -46,7 +46,7 @@ if (isQuestionGroupType(guide)) {
|
||||
New
|
||||
<span class='hidden sm:inline'>
|
||||
·
|
||||
{new Date(frontmatter.date).toLocaleString('default', {
|
||||
{new Date(frontmatter.date!).toLocaleString('default', {
|
||||
month: 'long',
|
||||
})}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class='container prose-h2:text-balance prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10'
|
||||
class:list={[
|
||||
'container prose prose-xl prose-h2:mb-3 prose-h2:mt-10 prose-h2:scroll-mt-5 prose-h2:text-balance prose-h2:text-3xl prose-h3:mt-2 prose-h3:scroll-mt-5 prose-h3:text-balance prose-h4:text-balance prose-h5:text-balance prose-h5:font-medium prose-blockquote:font-normal prose-code:bg-transparent prose-img:mt-1 prose-h2:sm:scroll-mt-10 prose-h3:sm:scroll-mt-10',
|
||||
className,
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,7 @@ import { showLoginPopup } from '../../lib/popup';
|
||||
import { VoteButton } from './VoteButton.tsx';
|
||||
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
|
||||
import { SelectLanguages } from './SelectLanguages.tsx';
|
||||
import type { ProjectFrontmatter } from '../../lib/project.ts';
|
||||
import { ProjectSolutionModal } from './ProjectSolutionModal.tsx';
|
||||
import type { ProjectFileType } from '../../lib/project.ts';
|
||||
|
||||
export interface ProjectStatusDocument {
|
||||
_id?: string;
|
||||
@@ -65,7 +64,7 @@ type PageState = {
|
||||
};
|
||||
|
||||
type ListProjectSolutionsProps = {
|
||||
project: ProjectFrontmatter;
|
||||
project: ProjectFileType['data'];
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Badge } from '../Badge.tsx';
|
||||
import type {
|
||||
ProjectDifficultyType,
|
||||
ProjectFileType,
|
||||
} from '../../lib/project.ts';
|
||||
import type { ProjectFileType } from '../../lib/project.ts';
|
||||
import { Users } from 'lucide-react';
|
||||
import { formatCommaNumber } from '../../lib/number.ts';
|
||||
import type { ProjectDifficultyType } from '../../content/project.ts';
|
||||
|
||||
type ProjectCardProps = {
|
||||
project: ProjectFileType;
|
||||
@@ -20,7 +18,7 @@ const badgeVariants: Record<ProjectDifficultyType, string> = {
|
||||
export function ProjectCard(props: ProjectCardProps) {
|
||||
const { project, userCount = 0 } = props;
|
||||
|
||||
const { frontmatter, id } = project;
|
||||
const { data: frontmatter, slug: id } = project;
|
||||
|
||||
return (
|
||||
<a
|
||||
|
||||
@@ -2,16 +2,16 @@ import { ProjectCard } from './ProjectCard.tsx';
|
||||
import { HeartHandshake, Trash2 } from 'lucide-react';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import { useMemo, useState } from 'react';
|
||||
import {
|
||||
projectDifficulties,
|
||||
type ProjectDifficultyType,
|
||||
type ProjectFileType,
|
||||
} from '../../lib/project.ts';
|
||||
import { type ProjectFileType } from '../../lib/project.ts';
|
||||
import {
|
||||
deleteUrlParam,
|
||||
getUrlParams,
|
||||
setUrlParams,
|
||||
} from '../../lib/browser.ts';
|
||||
import {
|
||||
projectDifficulties,
|
||||
type ProjectDifficultyType,
|
||||
} from '../../content/project.ts';
|
||||
|
||||
type DifficultyButtonProps = {
|
||||
difficulty: ProjectDifficultyType;
|
||||
@@ -56,7 +56,7 @@ export function ProjectsList(props: ProjectsListProps) {
|
||||
const result = new Map<ProjectDifficultyType, ProjectFileType[]>();
|
||||
|
||||
for (const project of projects) {
|
||||
const difficulty = project.frontmatter.difficulty;
|
||||
const difficulty = project.data.difficulty;
|
||||
|
||||
if (!result.has(difficulty)) {
|
||||
result.set(difficulty, []);
|
||||
@@ -78,6 +78,7 @@ export function ProjectsList(props: ProjectsListProps) {
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{projectDifficulties.map((projectDifficulty) => (
|
||||
<DifficultyButton
|
||||
key={projectDifficulty}
|
||||
onClick={() => {
|
||||
setDifficulty(projectDifficulty);
|
||||
setUrlParams({ difficulty: projectDifficulty });
|
||||
@@ -119,18 +120,24 @@ export function ProjectsList(props: ProjectsListProps) {
|
||||
|
||||
{matchingProjects
|
||||
.sort((project) => {
|
||||
return project.frontmatter.difficulty === 'beginner'
|
||||
return project.data.difficulty === 'beginner'
|
||||
? -1
|
||||
: project.frontmatter.difficulty === 'intermediate'
|
||||
: project.data.difficulty === 'intermediate'
|
||||
? 0
|
||||
: 1;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.frontmatter.sort - b.frontmatter.sort;
|
||||
return a.data.sort - b.data.sort;
|
||||
})
|
||||
.map((matchingProject) => {
|
||||
const count = userCounts[matchingProject?.id] || 0;
|
||||
return <ProjectCard project={matchingProject} userCount={count} />;
|
||||
const count = userCounts[matchingProject?.slug] || 0;
|
||||
return (
|
||||
<ProjectCard
|
||||
key={matchingProject.slug}
|
||||
project={matchingProject}
|
||||
userCount={count}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,9 @@ import {
|
||||
setUrlParams,
|
||||
} from '../../lib/browser.ts';
|
||||
import { CategoryFilterButton } from '../Roadmaps/CategoryFilterButton.tsx';
|
||||
import {
|
||||
projectDifficulties,
|
||||
type ProjectFileType,
|
||||
} from '../../lib/project.ts';
|
||||
import { type ProjectFileType } from '../../lib/project.ts';
|
||||
import { ProjectCard } from './ProjectCard.tsx';
|
||||
import { projectDifficulties } from '../../content/project.ts';
|
||||
|
||||
type ProjectGroup = {
|
||||
id: string;
|
||||
@@ -28,7 +26,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
const { roadmapsProjects, userCounts } = props;
|
||||
const allUniqueProjectIds = new Set<string>(
|
||||
roadmapsProjects.flatMap((group) =>
|
||||
group.projects.map((project) => project.id),
|
||||
group.projects.map((project) => project.slug),
|
||||
),
|
||||
);
|
||||
const allUniqueProjects = useMemo(
|
||||
@@ -37,7 +35,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
.map((id) =>
|
||||
roadmapsProjects
|
||||
.flatMap((group) => group.projects)
|
||||
.find((project) => project.id === id),
|
||||
.find((project) => project.slug === id),
|
||||
)
|
||||
.filter(Boolean) as ProjectFileType[],
|
||||
[allUniqueProjectIds],
|
||||
@@ -67,8 +65,8 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
const sortedVisibleProjects = useMemo(
|
||||
() =>
|
||||
visibleProjects.sort((a, b) => {
|
||||
const projectADifficulty = a?.frontmatter.difficulty || 'beginner';
|
||||
const projectBDifficulty = b?.frontmatter.difficulty || 'beginner';
|
||||
const projectADifficulty = a?.data.difficulty || 'beginner';
|
||||
const projectBDifficulty = b?.data.difficulty || 'beginner';
|
||||
return (
|
||||
projectDifficulties.indexOf(projectADifficulty) -
|
||||
projectDifficulties.indexOf(projectBDifficulty)
|
||||
@@ -189,7 +187,7 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
<ProjectCard
|
||||
key={project.id}
|
||||
project={project}
|
||||
userCount={userCounts[project.id] || 0}
|
||||
userCount={userCounts[project.slug] || 0}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
---
|
||||
import {
|
||||
getGuideTableOfContent,
|
||||
type GuideFileType,
|
||||
HeadingGroupType,
|
||||
} from '../../lib/guide';
|
||||
import { getGuideTableOfContent, type HeadingGroupType } from '../../lib/guide';
|
||||
import MarkdownFile from '../MarkdownFile.astro';
|
||||
import { TableOfContent } from '../TableOfContent/TableOfContent';
|
||||
import { markdownToHtml, replaceVariables } from '../../lib/markdown';
|
||||
import { QuestionGroupType } from '../../lib/question-group';
|
||||
import type { QuestionGroupType } from '../../lib/question-group';
|
||||
import { QuestionsList } from './QuestionsList';
|
||||
|
||||
interface Props {
|
||||
@@ -16,19 +12,17 @@ interface Props {
|
||||
|
||||
const { questionGroup } = Astro.props;
|
||||
|
||||
const allHeadings = questionGroup.getHeadings();
|
||||
const { headings: allHeadings, Content } = await questionGroup.render();
|
||||
const tableOfContent: HeadingGroupType[] = [
|
||||
...getGuideTableOfContent(allHeadings),
|
||||
{
|
||||
depth: 2,
|
||||
title: 'Test with Flashcards',
|
||||
text: 'Test yourself with Flashcards',
|
||||
children: [],
|
||||
slug: 'test-with-flashcards',
|
||||
text: 'Test yourself with Flashcards',
|
||||
},
|
||||
{
|
||||
depth: 2,
|
||||
title: 'Questions List',
|
||||
children: [
|
||||
{
|
||||
depth: 2,
|
||||
@@ -58,7 +52,7 @@ const tableOfContent: HeadingGroupType[] = [
|
||||
];
|
||||
|
||||
const showTableOfContent = tableOfContent.length > 0;
|
||||
const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
const { data: guideFrontmatter, author } = questionGroup;
|
||||
---
|
||||
|
||||
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
|
||||
@@ -86,20 +80,20 @@ const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
author && (
|
||||
<p class='my-0 flex items-center justify-start text-sm text-gray-400'>
|
||||
<a
|
||||
href={`/authors/${author?.id}`}
|
||||
href={`/authors/${author?.slug}`}
|
||||
class='inline-flex items-center font-medium underline-offset-2 hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
alt={author.data.name}
|
||||
src={author.data.imageUrl}
|
||||
class='mb-0 mr-2 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
{author.data.name}
|
||||
</a>
|
||||
<span class='mx-2 hidden sm:inline'>·</span>
|
||||
<a
|
||||
class='hidden underline-offset-2 hover:text-gray-600 sm:inline'
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/question-groups/${questionGroup.id}`}
|
||||
href={`https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/question-groups/${questionGroup.slug}`}
|
||||
target='_blank'
|
||||
>
|
||||
Improve this Guide
|
||||
@@ -107,7 +101,7 @@ const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<questionGroup.Content />
|
||||
<Content />
|
||||
|
||||
<h2 id='test-with-flashcards'>Test yourself with Flashcards</h2>
|
||||
<p>
|
||||
@@ -116,7 +110,7 @@ const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
</p>
|
||||
<div class='mx-0 sm:-mb-32'>
|
||||
<QuestionsList
|
||||
groupId={questionGroup.id}
|
||||
groupId={questionGroup.slug}
|
||||
questions={questionGroup.questions}
|
||||
client:load
|
||||
/>
|
||||
@@ -136,8 +130,8 @@ const { frontmatter: guideFrontmatter, author } = questionGroup;
|
||||
</h3>
|
||||
{questionGroup.questions
|
||||
.filter((q) => {
|
||||
return q.topics
|
||||
.map((t) => t.toLowerCase())
|
||||
return q?.topics
|
||||
?.map((t) => t.toLowerCase())
|
||||
.includes(questionLevel);
|
||||
})
|
||||
.map((q) => (
|
||||
|
||||
70
src/components/RoadmapAlert.tsx
Normal file
70
src/components/RoadmapAlert.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
BadgeCheck,
|
||||
HeartHandshake,
|
||||
Telescope,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { cn } from '../lib/classname';
|
||||
|
||||
type RoadmapAlertProps = {
|
||||
title: string;
|
||||
badgeText?: string;
|
||||
description: string | ReactNode;
|
||||
floatingIcon: LucideIcon;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function RoadmapAlert(props: RoadmapAlertProps) {
|
||||
const {
|
||||
title,
|
||||
badgeText,
|
||||
description,
|
||||
floatingIcon: FloatingIcon,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative mb-3 w-full rounded-xl bg-yellow-100 px-4 py-3 text-yellow-800',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<h2 className="flex items-center text-base font-semibold text-yellow-800 sm:text-lg">
|
||||
{title}{' '}
|
||||
{badgeText && (
|
||||
<span className="ml-1.5 rounded-md border border-yellow-500 bg-yellow-200 px-1.5 text-xs uppercase tracking-wide text-yellow-800">
|
||||
{badgeText}
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
<p className="mb-2 mt-1 text-balance">{description}</p>
|
||||
<p className="mb-1.5 mt-2 flex flex-col gap-2 text-sm md:flex-row">
|
||||
<a
|
||||
href="/roadmaps"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 bg-yellow-200 px-2 py-1 text-yellow-800 transition-colors hover:bg-yellow-300"
|
||||
>
|
||||
<BadgeCheck size={15} />
|
||||
Visit Official Roadmaps
|
||||
</a>
|
||||
<a
|
||||
href="/community"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
|
||||
>
|
||||
<HeartHandshake size={15} />
|
||||
Community Roadmaps
|
||||
</a>
|
||||
<a
|
||||
href="/ai/explore"
|
||||
className="flex items-center gap-1.5 rounded-md border border-yellow-600 px-2 py-1 text-yellow-700 transition-colors hover:bg-yellow-300 hover:text-yellow-800"
|
||||
>
|
||||
<Telescope size={15} />
|
||||
AI Generated Roadmaps
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<FloatingIcon className="pointer-events-none absolute right-2 top-2 hidden h-12 w-12 text-yellow-500 opacity-50 sm:block md:bottom-2 md:top-auto" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
src/components/Roadmaps/RoadmapCard.tsx
Normal file
31
src/components/Roadmaps/RoadmapCard.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useIsMounted } from '../../hooks/use-is-mounted';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
|
||||
import type { GroupType } from './RoadmapsPage';
|
||||
|
||||
type RoadmapCardProps = {
|
||||
roadmap: GroupType['roadmaps'][number];
|
||||
};
|
||||
|
||||
export function RoadmapCard(props: RoadmapCardProps) {
|
||||
const { roadmap } = props;
|
||||
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
return (
|
||||
<a
|
||||
key={roadmap.link}
|
||||
className="relative rounded-md border bg-white px-3 py-2 text-left text-sm shadow-sm transition-all hover:border-gray-300 hover:bg-gray-50"
|
||||
href={roadmap.link}
|
||||
>
|
||||
{roadmap.title}
|
||||
|
||||
{isMounted && (
|
||||
<MarkFavorite
|
||||
resourceId={roadmap.link.split('/').pop()!}
|
||||
resourceType="roadmap"
|
||||
className="data-[is-favorite=true]:opacity-35"
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
getUrlParams,
|
||||
setUrlParams,
|
||||
} from '../../lib/browser.ts';
|
||||
import { RoadmapCard } from './RoadmapCard.tsx';
|
||||
import { httpGet } from '../../lib/http.ts';
|
||||
import type { UserProgressResponse } from '../HeroSection/FavoriteRoadmaps.tsx';
|
||||
import { isLoggedIn } from '../../lib/jwt.ts';
|
||||
|
||||
const groupNames = [
|
||||
'Absolute Beginners',
|
||||
@@ -27,7 +31,7 @@ const groupNames = [
|
||||
|
||||
type AllowGroupNames = (typeof groupNames)[number];
|
||||
|
||||
type GroupType = {
|
||||
export type GroupType = {
|
||||
group: AllowGroupNames;
|
||||
roadmaps: {
|
||||
title: string;
|
||||
@@ -281,6 +285,12 @@ const groups: GroupType[] = [
|
||||
type: 'skill',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
{
|
||||
title: 'Redis',
|
||||
link: '/redis',
|
||||
type: 'skill',
|
||||
otherGroups: ['Web Development'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -473,6 +483,37 @@ export function RoadmapsPage() {
|
||||
]);
|
||||
}, [activeGroup]);
|
||||
|
||||
async function loadProgress() {
|
||||
const { response: progressList, error } =
|
||||
await httpGet<UserProgressResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-get-hero-roadmaps`,
|
||||
);
|
||||
|
||||
if (error || !progressList) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressList?.forEach((progress) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('mark-favorite', {
|
||||
detail: {
|
||||
resourceId: progress.resourceId,
|
||||
resourceType: progress.resourceType,
|
||||
isFavorite: progress.isFavorite,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadProgress().finally(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const { g } = getUrlParams() as { g: AllowGroupNames };
|
||||
if (!g) {
|
||||
@@ -547,13 +588,7 @@ export function RoadmapsPage() {
|
||||
|
||||
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
|
||||
{group.roadmaps.map((roadmap) => (
|
||||
<a
|
||||
key={roadmap.link}
|
||||
className="rounded-md border bg-white px-3 py-2 text-left text-sm shadow-sm transition-all hover:border-gray-300 hover:bg-gray-50"
|
||||
href={roadmap.link}
|
||||
>
|
||||
{roadmap.title}
|
||||
</a>
|
||||
<RoadmapCard roadmap={roadmap} key={roadmap.link} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,15 +87,13 @@ export function ProfileUsername(props: ProfileUsernameProps) {
|
||||
{currentUsername !== username && username && isUnique && (
|
||||
<span className="text-xs text-green-600">
|
||||
URL after update{' '}
|
||||
<a
|
||||
href={`${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/u/${username}`}
|
||||
target="_blank"
|
||||
<span
|
||||
className={
|
||||
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 text-xs font-medium text-purple-700 transition-colors hover:bg-purple-500 hover:text-white'
|
||||
'ml-0.5 rounded-md border border-purple-500 px-1.5 py-0.5 text-xs font-medium text-purple-700 transition-colors'
|
||||
}
|
||||
>
|
||||
roadmap.sh/u/{username}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -71,6 +71,7 @@ export function UpdatePublicProfileForm() {
|
||||
const [profileRoadmaps, setProfileRoadmaps] = useState<RoadmapType[]>([]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isProfileUpdated, setIsProfileUpdated] = useState(false);
|
||||
|
||||
const { isCopied, copyText } = useCopyText();
|
||||
|
||||
@@ -109,6 +110,7 @@ export function UpdatePublicProfileForm() {
|
||||
|
||||
await loadProfileSettings();
|
||||
toast.success('Profile updated successfully');
|
||||
setIsProfileUpdated(true);
|
||||
};
|
||||
|
||||
const loadProfileSettings = async () => {
|
||||
@@ -593,6 +595,42 @@ export function UpdatePublicProfileForm() {
|
||||
>
|
||||
{isLoading ? 'Please wait..' : 'Save Profile'}
|
||||
</button>
|
||||
{isProfileUpdated && publicProfileUrl && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex shrink-0 flex-row items-center gap-1 rounded-lg border border-black py-1.5 pl-2.5 pr-3.5 text-xs uppercase text-black transition-colors hover:bg-black hover:text-white',
|
||||
isCopied
|
||||
? 'border-green-600 bg-green-600 text-white hover:bg-green-600 hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={() => {
|
||||
copyText(`${window.location.origin}${publicProfileUrl}`);
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckCircle className="size-4" />
|
||||
Copied Profile URL
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="size-4" />
|
||||
Copy Profile URL
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<a
|
||||
className='flex shrink-0 flex-row items-center gap-1 rounded-lg border border-black py-1.5 pl-2.5 pr-3.5 text-xs uppercase text-black transition-colors hover:bg-black hover:text-white'
|
||||
href={publicProfileUrl}
|
||||
target="_blank"
|
||||
>
|
||||
<ArrowUpRight className="size-4" />
|
||||
View Profile
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Globe,
|
||||
LinkedinIcon,
|
||||
Mail,
|
||||
Pencil,
|
||||
Twitter,
|
||||
} from 'lucide-react';
|
||||
import type { GetPublicProfileResponse } from '../../api/user';
|
||||
@@ -15,11 +16,12 @@ type UserPublicProfileHeaderProps = {
|
||||
export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
|
||||
const { userDetails } = props;
|
||||
|
||||
const { name, links, publicConfig, avatar, email } = userDetails;
|
||||
const { name, links, publicConfig, avatar, email, isOwnProfile } =
|
||||
userDetails;
|
||||
const { headline, isAvailableForHire, isEmailVisible } = publicConfig!;
|
||||
|
||||
return (
|
||||
<div className="container flex items-center gap-6 rounded-xl border bg-white p-8">
|
||||
<div className="container relative flex items-center gap-6 rounded-xl border bg-white p-8">
|
||||
<img
|
||||
src={
|
||||
avatar
|
||||
@@ -27,7 +29,7 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
|
||||
: '/images/default-avatar.png'
|
||||
}
|
||||
alt={name}
|
||||
className="h-32 w-32 object-cover rounded-full"
|
||||
className="h-32 w-32 rounded-full object-cover"
|
||||
/>
|
||||
|
||||
<div>
|
||||
@@ -51,6 +53,16 @@ export function UserPublicProfileHeader(props: UserPublicProfileHeaderProps) {
|
||||
{isEmailVisible && <UserLink href={`mailto:${email}`} icon={Mail} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOwnProfile && (
|
||||
<a
|
||||
href="/account/update-profile"
|
||||
className="absolute right-4 top-4 flex items-center gap-1.5 text-sm text-gray-500 hover:text-black"
|
||||
>
|
||||
<Pencil className="h-3 w-3 stroke-2" />
|
||||
Edit Profile
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ export function UserPublicProjects(props: UserPublicProjectsProps) {
|
||||
return 0;
|
||||
}) || [];
|
||||
|
||||
if (!enrichedProjects.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
---
|
||||
import type { VideoFileType } from '../lib/video';
|
||||
import YouTubeAlert from './YouTubeAlert.astro';
|
||||
|
||||
export interface Props {
|
||||
video: VideoFileType;
|
||||
}
|
||||
|
||||
const { video } = Astro.props;
|
||||
const { frontmatter, author } = video;
|
||||
const { data: frontmatter, author } = video;
|
||||
---
|
||||
|
||||
<div class='border-b bg-white py-5 sm:py-12'>
|
||||
@@ -16,15 +15,15 @@ const { frontmatter, author } = video;
|
||||
class='hidden items-center justify-start text-gray-400 sm:flex sm:justify-center'
|
||||
>
|
||||
<a
|
||||
href={`/authors/${author.id}`}
|
||||
href={`/authors/${author.slug}`}
|
||||
class='inline-flex items-center font-medium hover:text-gray-600 hover:underline'
|
||||
>
|
||||
<img
|
||||
alt={author.frontmatter.name}
|
||||
src={author.frontmatter.imageUrl}
|
||||
alt={author.data.name}
|
||||
src={author.data.imageUrl}
|
||||
class='mr-2 inline h-5 w-5 rounded-full'
|
||||
/>
|
||||
{author.frontmatter.name}
|
||||
{author.data.name}
|
||||
</a>
|
||||
<span class='mx-1.5'>·</span>
|
||||
<span class='capitalize'>Illustrated Video</span>
|
||||
|
||||
@@ -6,21 +6,21 @@ export interface Props {
|
||||
}
|
||||
|
||||
const { video } = Astro.props;
|
||||
const { frontmatter, id } = video;
|
||||
const { data: frontmatter, slug: id } = video;
|
||||
---
|
||||
|
||||
<a
|
||||
class:list={[
|
||||
'block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b',
|
||||
'text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600',
|
||||
]}
|
||||
href={`/videos/${id}`}
|
||||
>
|
||||
<span class='group-hover:translate-x-2 transition-transform'>
|
||||
<span class='transition-transform group-hover:translate-x-2'>
|
||||
{frontmatter.title}
|
||||
|
||||
{
|
||||
frontmatter.isNew && (
|
||||
<span class='bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5'>
|
||||
<span class='ml-1.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900'>
|
||||
New
|
||||
<span class='hidden sm:inline'>
|
||||
·
|
||||
@@ -32,9 +32,9 @@ const { frontmatter, id } = video;
|
||||
)
|
||||
}
|
||||
</span>
|
||||
<span class='capitalize text-gray-500 text-xs hidden sm:block'>
|
||||
<span class='hidden text-xs capitalize text-gray-500 sm:block'>
|
||||
{frontmatter.duration}
|
||||
</span>
|
||||
|
||||
<span class='text-gray-400 text-xs block sm:hidden'> »</span>
|
||||
<span class='block text-xs text-gray-400 sm:hidden'> »</span>
|
||||
</a>
|
||||
|
||||
23
src/content/author.ts
Normal file
23
src/content/author.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
export const authorCollection = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
imageUrl: z.string(),
|
||||
employment: z
|
||||
.object({
|
||||
title: z.string(),
|
||||
company: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
social: z
|
||||
.object({
|
||||
linkedin: z.string().optional(),
|
||||
twitter: z.string().optional(),
|
||||
github: z.string().optional(),
|
||||
website: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
16
src/content/changelog.ts
Normal file
16
src/content/changelog.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
export const changelogCollection = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
seo: z
|
||||
.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
date: z.date(),
|
||||
}),
|
||||
});
|
||||
25
src/content/changelogs/leaderboard-page.md
Normal file
25
src/content/changelogs/leaderboard-page.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: 'New Dashboard, Leaderboards and Projects'
|
||||
description: 'New leaderboard page showing the most active users'
|
||||
seo:
|
||||
title: 'Leaderboard Page - roadmap.sh'
|
||||
description: ''
|
||||
date: 2024-09-13
|
||||
---
|
||||
|
||||
TL;DR: new dashboard, leaderboard page and projects page.
|
||||
|
||||
- New dashboard for logged-in users
|
||||
- New leaderboard page
|
||||
- Projects page listing all projects
|
||||
- Ability to stop a started project
|
||||
- Frontend and backend content improvements
|
||||
- Bug fixes
|
||||
|
||||

|
||||
|
||||
We just launched a dedicated dashboard for logged-in users to showing progress, projects, bookmarks and more. You can still access the old homepage by visiting [this page](https://roadmap.sh/home).
|
||||
|
||||
We also launched a new [leaderboard page](/leaderboard) showing the most active users, users who completed most projects and more.
|
||||
|
||||
There is also a [new projects page](/projects) where you can see all the projects you have been working on. You can also now stop a started project.
|
||||
12
src/content/changelogs/new-dashboard-page.md
Normal file
12
src/content/changelogs/new-dashboard-page.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 'New Dashboard Page'
|
||||
description: 'We have added a new dashboard page to help you track your progress'
|
||||
seo:
|
||||
title: 'New Dashboard Page - roadmap.sh'
|
||||
description: 'We have added a new dashboard page to help you track your progress'
|
||||
date: 2024-09-12
|
||||
---
|
||||
|
||||
We have revamped the dashboard page for logged-in users. The new dashboard page will help you track your progress and see your overall progress in a single view. We have also added a new progress bar to help you visualize your progress.
|
||||
|
||||
If you want to access the guest homepage, you check check it out [here](/home).
|
||||
15
src/content/config.ts
Normal file
15
src/content/config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { authorCollection } from './author';
|
||||
import { changelogCollection } from './changelog';
|
||||
import { guideCollection } from './guide';
|
||||
import { projectCollection } from './project';
|
||||
import { questionGroupCollection } from './question-group';
|
||||
import { videoCollection } from './video';
|
||||
|
||||
export const collections = {
|
||||
authors: authorCollection,
|
||||
guides: guideCollection,
|
||||
'question-groups': questionGroupCollection,
|
||||
projects: projectCollection,
|
||||
videos: videoCollection,
|
||||
changelogs: changelogCollection,
|
||||
};
|
||||
25
src/content/guide.ts
Normal file
25
src/content/guide.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
export const guideCollection = defineCollection({
|
||||
type: 'content',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
authorId: z.string(),
|
||||
canonicalUrl: z.string().optional(),
|
||||
excludedBySlug: z.string().optional(),
|
||||
seo: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
ogImageUrl: z.string().optional(),
|
||||
}),
|
||||
isNew: z.boolean(),
|
||||
type: z.enum(['visual', 'textual']),
|
||||
date: z.date(),
|
||||
sitemap: z.object({
|
||||
priority: z.number(),
|
||||
changefreq: z.enum(['daily', 'weekly', 'monthly', 'yearly']),
|
||||
}),
|
||||
tags: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'What is a DevOps Engineer? Responsbilities & Roles in @currentYear@'
|
||||
title: 'What is a DevOps Engineer? Responsibilities & Roles in @currentYear@'
|
||||
description: 'Explore the responsibilities and roles of a DevOps Engineer in @currentYear@. Gain insights into the evolving field of DevOps and what it takes to succeed.'
|
||||
authorId: ekene
|
||||
excludedBySlug: '/devops/devops-engineer'
|
||||
@@ -21,13 +21,13 @@ tags:
|
||||
|
||||

|
||||
|
||||
Are you a developer monitoring recent changes in the ecosystem, looking to change careers or pick up new skills in 2024? If your choice is DevOps, you might be wondering what it entails, what it will take to become one in 2024, and how it is affected by the recent changes in the tech ecosystem.
|
||||
Are you a developer monitoring recent changes in the ecosystem, looking to change careers or pick up new skills in 2024? If your choice is [DevOps](https://roadmap.sh/devops), you might be wondering what it entails, what it will take to become one in 2024, and how it is affected by the recent changes in the tech ecosystem.
|
||||
|
||||
In recent years, the technology ecosystem has experienced a constant shift in the way hiring managers reach out, companies hire, and the roles and responsibilities described in job postings. Particularly, 2023 proved to be a challenging year as layoffs in the technology sector grew significantly, with more than [262,000 employees laid off across 1,180 firms](https://www.statista.com/statistics/199999/worldwide-tech-layoffs-covid-19/).
|
||||
|
||||
Despite this change, DevOps, a field within the same ecosystem, has experienced continuous growth. In fact, the DevOps market size is expected to grow to [25.5 billion USD by 2028](https://www.marketsandmarkets.com/Market-Reports/devops-market-824.html#:~:text=The%20global%20DevOps%20market%20size,USD%2010.4%20billion%20in%202023.). This indicates that the roles and responsibilities of a DevOps engineer in the modern technology environment will evolve alongside this increasing demand.
|
||||
|
||||
In this guide, we'll discuss the roles and responsibilities of a [DevOps engineer](https://roadmap.sh/devops), the importance of DevOps in teams, common roles within a DevOps team, and best practices for DevOps teams. Finally, the guide will offer roadmaps for your DevOps journey.
|
||||
In this guide, we'll discuss the roles and responsibilities of a DevOps engineer, the importance of DevOps in teams, common roles within a DevOps team, and best practices for DevOps teams. Finally, the guide will offer roadmaps for your DevOps journey.
|
||||
|
||||
A DevOps engineer's roles and responsibilities include:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user