Compare commits

...

238 Commits

Author SHA1 Message Date
Kamran Ahmed
9663ba019e Improve UI 2025-03-18 18:17:28 +00:00
Arik Chakma
f82eb09f60 feat: delete ai course 2025-03-18 23:54:20 +06:00
Kamran Ahmed
a60bcb5a45 Update message 2025-03-18 16:29:59 +00:00
Kamran Ahmed
3cf2ad9b25 Update course content 2025-03-18 16:27:00 +00:00
Kamran Ahmed
6449c24398 Update AI chat popup 2025-03-18 01:28:56 +00:00
Kamran Ahmed
eaddc81383 Merge branch 'master' of github.com:kamranahmedse/developer-roadmap 2025-03-18 00:39:25 +00:00
Jawher Kl
b6e0d566a6 fix: broken link (#8334) 2025-03-18 00:39:11 +00:00
Gleison
01f93d95fb feat: add content for StyleCop (#8337)
* Added content for StyleCop section

* Update src/data/roadmaps/aspnet-core/content/stylecop-rules@R7Qk5hsEIl9dspQXdaJAJ.md

* Update src/data/roadmaps/aspnet-core/content/stylecop-rules@R7Qk5hsEIl9dspQXdaJAJ.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-03-18 00:39:11 +00:00
Kamran Ahmed
fbd39e9079 Moving next should be mark it as done 2025-03-18 00:32:17 +00:00
Kamran Ahmed
3bc00b5b1a Storing fine-tune data 2025-03-18 00:32:17 +00:00
Kamran Ahmed
340ae002ca Improve fine-tuning 2025-03-18 00:32:17 +00:00
Kamran Ahmed
98d8510b60 Add UI 2025-03-18 00:32:17 +00:00
Kamran Ahmed
a82a0e6efb Moving next should be mark it as done 2025-03-18 00:30:46 +00:00
Kamran Ahmed
b17ba1b009 Storing fine-tune data 2025-03-18 00:07:23 +00:00
Kamran Ahmed
a07a5af543 Improve fine-tuning 2025-03-17 23:53:21 +00:00
Jawher Kl
16db649baf fix: broken link (#8334) 2025-03-18 01:22:18 +06:00
Kamran Ahmed
017fe3e0a4 Add UI 2025-03-17 17:15:35 +00:00
Gleison
1f727d2e17 feat: add content for StyleCop (#8337)
* Added content for StyleCop section

* Update src/data/roadmaps/aspnet-core/content/stylecop-rules@R7Qk5hsEIl9dspQXdaJAJ.md

* Update src/data/roadmaps/aspnet-core/content/stylecop-rules@R7Qk5hsEIl9dspQXdaJAJ.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-03-17 21:57:14 +06:00
Kamran Ahmed
281f6f369c Remove old guide flags 2025-03-17 15:28:54 +00:00
Kamran Ahmed
eb5e5fadcc Add python FAQs 2025-03-17 13:27:55 +00:00
Kamran Ahmed
4996d51340 Add JavaScript faqs 2025-03-17 12:46:12 +00:00
Kamran Ahmed
ea944a001e Add AI and Data Scientist Roadmap FAQs 2025-03-17 12:25:34 +00:00
Kamran Ahmed
6d28ab40a8 Update signup popup message 2025-03-16 00:19:24 +00:00
Arik Chakma
ebb88721b6 feat: ai course pagination (#8329) 2025-03-15 12:32:24 +00:00
Kamran Ahmed
8878d04f98 Remove autocomplete from ai roadmap search 2025-03-15 04:28:44 +00:00
Kamran Ahmed
1085c33dc4 Regenerate functionality 2025-03-14 21:10:57 +00:00
Kamran Ahmed
6b9007c530 Enable AI tutor 2025-03-14 19:53:42 +00:00
Kamran Ahmed
5ff89fa184 Add regenerate lessons 2025-03-14 14:46:27 +00:00
Kamran Ahmed
dfff959916 Remove ai course lessons before generation 2025-03-14 13:28:53 +00:00
Kamran Ahmed
3ba9abe7e3 Improve AI courses 2025-03-14 13:17:04 +00:00
Kamran Ahmed
fbd149f955 Disable AI tutor 2025-03-14 11:39:58 +00:00
Kamran Ahmed
d78fd6ccff Refactor AI course view 2025-03-14 04:25:23 +00:00
Kamran Ahmed
2be8dbe0c2 Update sidebar ui for courses 2025-03-14 03:24:59 +00:00
Kamran Ahmed
79c6e2be53 refactor: ai-courses (#8327)
* Refactor ai courses

* Refactor

* Regenerate roadmap functionality

* Title and difficulty to refresh also

* Add course regeneration

* Improve the non paid user headings

* Update

* Improve back button logic

* Is paid user checks
2025-03-14 03:05:07 +00:00
github-actions[bot]
cc5585171c chore: update roadmap content json (#8326)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-14 02:14:57 +00:00
Kamran Ahmed
38cd727e48 Add ai tutor link in navigation 2025-03-13 12:06:41 +00:00
Kamran Ahmed
fda56a5d30 Remove geeksforgeeks links 2025-03-13 11:48:33 +00:00
Kamran Ahmed
e27146d549 Add billing 2025-03-13 01:16:19 +00:00
Kamran Ahmed
eb95da0bb0 Refactor perks 2025-03-12 15:37:14 +00:00
Kamran Ahmed
554e61947b Update design 2025-03-12 15:23:59 +00:00
Kamran Ahmed
107ae1923b Update back text for ai tutor 2025-03-12 14:18:05 +00:00
Kamran Ahmed
cb64894e49 feat: add ai course generator (#8322)
* Course landing page

* Add ai course page

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip: error handling

* wip

* wip

* wip

* wip: ai course progress

* wip

* wip

* wip

* feat: code highlighting

* feat: usage limit

* feat: follow up message

* Update UI

* wip

* Add course content

* wip: autogrow textarea & examples

* Update types

* Update

* fix: add highlight to the AI chat

* UI changes

* Refactor

* Update

* Improve outline style

* Improve spacing

* Improve spacing

* UI changes for sidebar

* Update UI for sidebar

* Improve course UI

* Mark done, undone

* Add toggle lesson done/undone

* Update forward backward UI

* wip

* Minor ui change

* Responsiveness of sidebar

* wip

* wip

* wip: billing page

* wip

* Update UI

* fix: hide upgrade if paid user

* feat: token usage

* feat: list ai courses

* fix: limit for followup

* Course content responsiveness

* Make course content responsive

* Responsiveness

* Outline button

* Responsiveness of course content

* Responsiveness of course content

* Add course upgrade button

* Update design for upgrade

* Improve logic for upgrade and limits button

* Limits and errors

* Add lesson count

* Add course card

* Improve UI for course generator

* Update course functionality

* Refactor AI course generation

* Responsiveness of screen

* Improve

* Add responsiveness

* Improve empty billing page design

* Add empty billing screen

* Update UI for billing page

* Update UI for billing page

* Update UI for billing page

* Update billing page design

* Update

* Remove sidebar

* Update

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-03-12 13:17:38 +00:00
Jawher Kl
faf43f7905 fix: broken link (#8254) 2025-03-12 02:43:46 +06:00
Ed Lan
c9f450e166 fix: ai data scientist meta (#8320) 2025-03-12 02:36:20 +06:00
Vedansh
3b6d620ed8 feat: refractor flutter roadmap content (#8311)
* refractor - 100, 101, 102 topics

* refractor 103

* refractor 104 105

* refractor 106

* refractor 107 108 content

* refractor 109 content

* refractor 110 to 119 content.
2025-03-11 18:59:56 +06:00
github-actions[bot]
bd937f5dbe chore: update roadmap content json (#8317)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-11 18:59:29 +06:00
Kamran Ahmed
cf05610b86 Update course page text 2025-03-11 12:47:07 +00:00
Alexandre D. Roberge
8b61bbfcbb fix: typo in filename 2025-03-10 18:44:18 +06:00
github-actions[bot]
2c39611b47 chore: update roadmap content json (#8312)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-10 18:43:41 +06:00
Naser Mohamed
475cb85600 fix: remove member only content (#8307)
The mentioned resource was not free (required a member subscription)
2025-03-09 22:40:50 +06:00
github-actions[bot]
37de8700d5 chore: update roadmap content json (#8304)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-08 08:04:12 +06:00
Kamran Ahmed
9db05bddbd Fix broken link 2025-03-06 11:46:06 +00:00
Matthew Bill
4bda81bf52 Add resource for trust building (#8300)
Added details about the trust equation to trust/influence in theengineering-manager roadmap
2025-03-06 11:45:07 +00:00
github-actions[bot]
00c5254ea9 chore: update roadmap content json (#8301)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-06 11:31:27 +00:00
Kamran Ahmed
bc97fc4c03 Add java roadmap content 2025-03-05 14:06:58 +00:00
Kamran Ahmed
f953b96d52 Add updated java assets 2025-03-05 12:14:26 +00:00
Kamran Ahmed
40793efe4e Migrate Java roadmap 2025-03-05 11:49:22 +00:00
github-actions[bot]
22a29605d8 chore: update roadmap content json (#8288)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-03-04 21:15:08 +06:00
Devin DeShun
cbfd4f7fcb Replace a link for sheet (#8287)
Replace Microsoft Access "replace function" link with Microsoft Excel "replace function" link.
2025-02-28 23:51:45 +00:00
Igor Kustov
992cf82e5c feat: add resources to rate-limiting topic (#8282)
* Added few resources to rate-limiting topic

* Added 'What is rate limiting? | Rate limiting and bots' article
2025-02-28 19:26:31 +06:00
Michelle
ea89ac864c fix: bookmarks typo (#8259) 2025-02-27 02:55:48 +06:00
github-actions[bot]
35a4a93ca7 chore: update roadmap content json (#8277)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-27 02:55:08 +06:00
Kamran Ahmed
2544d4b12d Add title question in devops roadmap 2025-02-26 18:42:30 +00:00
Igor Kustov
d6ee7ef76a Added article on http-caching topic (#8263) 2025-02-24 11:45:37 +00:00
Kamran Ahmed
daa2c03643 Update DevOps FAQs 2025-02-24 11:45:00 +00:00
github-actions[bot]
92a61e7c45 chore: update roadmap content json (#8255)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-22 17:50:00 +06:00
Kamran Ahmed
e6ad9b29d6 Add changelog entry for cloudflare + new dashboard 2025-02-21 19:40:18 +00:00
Kamran Ahmed
ea040b7912 Update navigation 2025-02-21 19:19:50 +00:00
Kamran Ahmed
28e874bdcd Update aspnet-core roadmap 2025-02-21 19:15:08 +00:00
Kamran Ahmed
4125c618d7 Add roadmaps to clean 2025-02-21 19:00:30 +00:00
Kamran Ahmed
088615d13f Add missing files 2025-02-21 18:30:00 +00:00
Kamran Ahmed
b02d07917d Migrate aspnet-core roadmap 2025-02-21 18:29:23 +00:00
Kamran Ahmed
9f1d44e542 Add clarity 2025-02-20 17:46:12 +00:00
Kamran Ahmed
587e8a197f Update quote message 2025-02-20 17:31:13 +00:00
Vedansh
5023e962b3 feat: add content to cloudflare roadmap (#8248)
* add content to prerequisites and core dev skills.

* add content and topic links to each topic.
few refractors too.
---
2025-02-20 17:17:51 +00:00
Kamran Ahmed
28c41b02c0 Update quote message 2025-02-20 17:15:49 +00:00
Kamran Ahmed
7a80313f1d Add author credentials on page 2025-02-20 15:42:25 +00:00
Kamran Ahmed
6bc1233253 Add author credentials on page 2025-02-20 15:41:28 +00:00
Kamran Ahmed
f82ca986c5 Update testimonial 2025-02-20 13:33:52 +00:00
Kamran Ahmed
d45b08c5d3 Update testimonial 2025-02-20 13:33:52 +00:00
Kamran Ahmed
d0c6ae1835 Update 2025-02-20 13:33:52 +00:00
Vedansh
be2d3a0d0e feat: add content to few topics in cloudflare roadmap. (#8245)
* add content to prerequisites and core dev skills.

* Update src/data/roadmaps/cloudflare/content/basic-command-line-knowledge@q9oQTt_NqhdWvJfA5XH1V.md

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2025-02-20 13:10:17 +00:00
Kamran Ahmed
c81c799e44 Add testimonials 2025-02-20 13:06:01 +00:00
Artur
0bcf1b09bd Adding 2 videos links to Aws Roadmap - Lambda-Creating and Lambda-Layers subtopics (#8148)
* Added link video to Lambda content

Added a link video to show how create a lambda function in python

* Added link video do Lambda-layers content

Added a video showing use and how create a lambda layer to a script in pytho
2025-02-20 00:47:07 +00:00
Vedansh
f7f0270f75 refractor all topics. (#8168) 2025-02-20 00:45:52 +00:00
Eyob wondyifraw
8033ccbe6d clarify behavior of git diff and --unified option (#8169)
The previous documentation incorrectly described the behavior of `git diff`
and the purpose of the `--unified` option. This commit fixes the definition
to accurately reflect how `git diff` works and the role of `--unified`.

Changes:
- Clarify that `git diff` compares the working directory against the staging
  area for unstaged changes, not directly against the latest commit.
- Explain that `--unified` controls the number of context lines shown in the
  diff output, rather than being a mode for comparing unstaged changes.
- Add examples to illustrate the correct usage of `git diff` and `--unified`.

This update ensures users have a clear and accurate understanding of how to
review changes and customize diff output.
2025-02-20 00:45:30 +00:00
Andrew Garvin
694c208ee3 Fix typo (#8178) 2025-02-20 00:44:49 +00:00
Piotr Idzik
41c7388f63 Use consistent formatting (#8184)
* Use consistent space after `if`

* Use consistent space after `for`

* Add missing space between `)` and `{`
2025-02-20 00:44:30 +00:00
Sai Prithvi
2f2a9b2d32 fixed typo: act to acr (#8185) 2025-02-20 00:44:01 +00:00
Priyanshu Maurya
083ec6c0d7 Add ECS resource (#8186)
* Update 100-clusters.md

Contained the same additional resources as the ECS

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html

Changed to relevant one

(https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html)

* Update 100-clusters.md

added additional seperate link for ECS Container Agents

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html
2025-02-20 00:43:00 +00:00
Piotr Idzik
32690e98da Use gofmt compliant formatting (#8192) 2025-02-20 00:42:14 +00:00
Dom Jackson
ed8c2f3168 Swap out a 404ing link from Backend Architectural patterns with its new location (#8201)
* Update architectural-patterns@tHiUpG9LN35E5RaHddMv5.md

This article is 404ing

* Swap out with new link
2025-02-20 00:41:21 +00:00
github-actions[bot]
1a9f53150b chore: update roadmap content json (#8244)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-20 00:40:43 +00:00
Jawher Kl
1779eef91b Update watermill resources (#8230)
* Update 100-watermill.md

Enhance Watermill description with additional resources

* Update src/data/roadmaps/golang/content/109-go-microservices/100-watermill.md

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2025-02-19 15:52:04 +00:00
Jawher Kl
d7b3f82d18 Update resources in game development (#8233) 2025-02-19 15:48:26 +00:00
Furkan Emin Can
fb34a7176c Update description of Godot game engine in game dev roadmap (#8234) 2025-02-19 15:46:51 +00:00
Furkan Emin Can
c5316929ba Update Godot resources (#8235)
* Add new video resources for Godot Engine in game dev roadmap

* Replace mobile video link with normal one in Godot Engine description
2025-02-19 15:45:53 +00:00
Furkan Emin Can
1ef1818327 Update resources related to C / C++ (#8237)
* Update resource link of C in game dev roadmap

* Add resources for C / C++ in server side game dev roadmap
2025-02-19 15:45:15 +00:00
Vo Chanh Hung
472c7f415b chore: update roadmap content json (#8238) 2025-02-19 15:44:52 +00:00
dejanualex
c43d294ab4 refactor phrasing for container runtime (#8242)
Signed-off-by: dejanu <dejanualexandru@gmail.com>
2025-02-19 15:44:34 +00:00
Kamran Ahmed
06747df054 Add platform demo 2025-02-19 14:36:32 +00:00
Kamran Ahmed
2aeb2ad65e Add course video on the course landing page 2025-02-19 14:28:40 +00:00
github-actions[bot]
3fd6e859df chore: update roadmap content json (#8227)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-19 14:59:45 +06:00
Kamran Ahmed
6d85bbe488 Add highlighting in reviews 2025-02-17 21:08:46 +00:00
Kamran Ahmed
5bb1252795 Add testimonials on course page 2025-02-17 21:07:04 +00:00
Kamran Ahmed
dfaf120314 Add testimonials 2025-02-17 21:00:04 +00:00
Kamran Ahmed
be5ccbbc0b Add testimonials 2025-02-17 20:05:10 +00:00
Kamran Ahmed
12950051d6 Update 2025-02-17 19:51:59 +00:00
Kamran Ahmed
03cd25b6c0 Update reviews section 2025-02-17 19:45:03 +00:00
Kamran Ahmed
829615ffec Add testimonials 2025-02-17 16:06:25 +00:00
Kamran Ahmed
6a24436418 Fix course broken UI 2025-02-17 13:35:09 +00:00
Kamran Ahmed
c726a1a342 Add workers kv resources 2025-02-17 13:33:06 +00:00
eqsdxr
7d7835ee9c Fix typos in content (#8218)
* Fix small typos

* Fix typo

* Fix typo
2025-02-17 12:37:45 +00:00
Mojtaba Kamyabi
17d30c0e8d Update 404 links in towarddatascience site to medium ones (#8221) 2025-02-17 12:37:05 +00:00
github-actions[bot]
62bd5c339f chore: update roadmap content json (#8216)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-16 09:22:22 +00:00
Jasper
111f702b9b Cloudflare Roadmap - Update Cloudflare Hono & Itty Router (#8211) 2025-02-15 17:31:11 +00:00
Jawher Kl
3dd115dce1 fix: classname import path
Resolves #8204
2025-02-14 18:40:28 +06:00
github-actions[bot]
75925cb53a chore: update roadmap content json (#8193)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-14 18:39:20 +06:00
Kamran Ahmed
de5bed02f8 Add SQL course link to sql roadmap 2025-02-13 15:06:42 +00:00
Kamran Ahmed
72c670570b Add cloudflare roadmap to get-started page 2025-02-12 23:52:31 +00:00
Kamran Ahmed
a450b4ed5a Add higher quality cloudflare roadmap 2025-02-12 22:44:28 +00:00
Kamran Ahmed
66be61efa3 Add cloudflare roadmap 2025-02-12 22:42:35 +00:00
Kamran Ahmed
caddd0f93d Fix ai roadamp UI 2025-02-11 22:10:23 +00:00
Arik Chakma
cc32cbe79c fix: editor roadmap rendering style (#8190) 2025-02-11 19:47:51 +06:00
Kamran Ahmed
f9d39db24a Remove personal button when loading 2025-02-11 13:40:58 +00:00
Kamran Ahmed
203bbc6eae Add dashboard redesign (#8189)
* Improve personal dashboard design

* Add projects toggle

* Improve UI for AI roadmaps

* Add builtin roadmaps and best practices

* Collapse and expand

* Move to separate files

* Refactor hero items group

* Collapse expand

* Add expand collapse in hero title

* Add collapse expand of groups

* Style updates

* Collapse expand

* Remove global collapse expand

* Update hero title

* Fix spacing

* Empty screen handling

* Add empty message

* Add profile button

* Add questions listing on dashboard

* Add guides and videos on dashboard

* Responsiveness

* Update messaging
2025-02-11 12:39:34 +00:00
Arik Chakma
31a852113f fix: member progress (#8188)
* fix: member progress

* fix: remove question check

* refactor: resource meta
2025-02-11 12:34:44 +00:00
Kamran Ahmed
66119e935b Fix broken label in AI roadmap 2025-02-11 12:12:54 +00:00
github-actions[bot]
3374fafe5b chore: update roadmap content json (#8183)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-09 23:55:11 +06:00
Piotr Idzik
8ed47a2e71 Make the code example c++20 compliant (#8177) 2025-02-09 05:32:46 +00:00
Shayan Shojaei
932b513d98 fix: topic file name 2025-02-07 22:43:15 +06:00
Kamran Ahmed
e0ae5dd309 Add new guide data science vs machine learning 2025-02-06 19:29:29 +00:00
Kamran Ahmed
70bc2a1038 Update guide authors 2025-02-06 19:15:43 +00:00
Kamran Ahmed
86c1120559 Add new guide data science vs data analytics 2025-02-06 19:13:56 +00:00
Kamran Ahmed
c3135e1470 Add new guide data science vs computer science 2025-02-06 19:05:30 +00:00
Kamran Ahmed
23346ec007 Add course author messageg 2025-02-06 15:46:30 +00:00
Kamran Ahmed
c92a183ef8 Add course author messageg 2025-02-06 14:32:43 +00:00
Jinhwan Kim
ca7888aa37 feat: bubble tea content 2025-02-06 18:34:30 +06:00
Klexus1
33b36a7017 fix: topic content
This change is to emphasize that container can be stopped without data loss.
2025-02-06 17:46:24 +06:00
Mohammed Hafeez
bff98e6448 feat: add rust interactive book
* Update index.md

Added an interactive web doc form which im learning rust.

* Update src/data/roadmaps/rust/content/100-introduction/index.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-02-06 17:11:01 +06:00
github-actions[bot]
5dd2bc439f chore: update roadmap content json (#8173)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-06 17:08:59 +06:00
Kamran Ahmed
24d10d212f Add devops projects on homepage 2025-02-04 18:06:54 +00:00
Kamran Ahmed
4127f77aac Add sql course changelog entry 2025-02-04 18:05:26 +00:00
Kamran Ahmed
dbebf593fc Fix z-index issues 2025-02-04 13:32:39 +00:00
Kamran Ahmed
0e0b550f98 Add how to become a fullstack developer guide 2025-02-04 12:53:34 +00:00
Kamran Ahmed
80741df13b Add go vs java guide 2025-02-04 02:17:25 +00:00
Kamran Ahmed
14b6aea3b1 Update messaging for course 2025-02-04 02:01:53 +00:00
Kamran Ahmed
e0da1e4f0e Fix issue where share solution is shown without submitting 2025-02-04 02:01:15 +00:00
Kamran Ahmed
5cc4b834d1 Improve UI for project solutions 2025-02-03 23:03:32 +00:00
Kamran Ahmed
314eb5d7d2 Refactor project solutoin row 2025-02-03 21:17:06 +00:00
Kamran Ahmed
ad2597f610 Update banner message 2025-02-03 19:57:18 +00:00
Kamran Ahmed
8d25eabe3a Add sorting to project solution listing 2025-02-03 19:45:42 +00:00
Kamran Ahmed
6186e12b05 Improve language filtering 2025-02-03 18:05:59 +00:00
Kamran Ahmed
158857c928 Add sql course banner 2025-02-03 14:22:20 +00:00
Kamran Ahmed
8e6959cc60 Fix z-index issue 2025-02-03 13:34:24 +00:00
Kamran Ahmed
e351f653a1 Add banner on top 2025-02-03 13:29:12 +00:00
Arik Chakma
83e315aef7 feat: add first login flag (#8161) 2025-02-03 11:06:22 +00:00
github-actions[bot]
b15bdd5f78 chore: update roadmap content json (#8154)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-02 09:40:01 +06:00
github-actions[bot]
0783330a70 chore: update roadmap content json (#8145)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-02-01 21:18:11 +06:00
Rikuya Osawa
7ec56fd1ff fix: replace unavailable resource link (#8152) 2025-02-01 13:14:07 +00:00
Kamran Ahmed
201632941a Handle redirect on purchase 2025-01-31 17:37:07 +00:00
Kamran Ahmed
126501b40a Add course page to index 2025-01-31 16:59:27 +00:00
Arik Chakma
34ba9162b2 feat: add course GA tracking (#8150) 2025-01-31 16:52:06 +00:00
Kamran Ahmed
e093eddabc Update course slug 2025-01-31 16:13:45 +00:00
Kamran Ahmed
1e4a4c96b8 Fix type issue 2025-01-31 14:50:59 +00:00
Kamran Ahmed
0c3ea981cb Add announcement 2025-01-31 12:50:53 +00:00
Kamran Ahmed
5de9539af5 Add platform demo 2025-01-31 12:43:41 +00:00
gavhu10
3722e5f3bd Added a resource concerning Python Modules and Packages (#8095) 2025-01-30 22:01:01 +00:00
Vedansh
7f90b8a0b0 fix topic link type. (#8098) 2025-01-30 22:00:06 +00:00
Rikuya Osawa
2bafd61f71 add article resource (#8125) 2025-01-30 21:54:25 +00:00
OLODiman11
adc66cf97c Fixed some typos on redis rodemap (#8131)
* fix: wrong link to redis rpop command

* fix: links not properly displaying because of a typo
2025-01-30 21:52:57 +00:00
Jérémy
c754a971c3 Fix typo in Symfony detail description (#8132) 2025-01-30 21:52:26 +00:00
Jovan Ivošević
dbda69fc23 Fix EPA definition (#8134)
* Updated EPA text for game-developer roadmap

* Removed reduntant whitespace
2025-01-30 21:52:09 +00:00
DombleGames
6f1087981c Fix array_fill example (#8138)
Fix: Use correct parameter name "count" instead of "num" in array_fill()

Updated the array_fill() function to use "count" as the second parameter, aligning with the official PHP documentation.
2025-01-30 21:51:47 +00:00
github-actions[bot]
ec8a0917c1 chore: update roadmap content json (#8140)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-30 21:50:54 +00:00
Kamran Ahmed
60e5e38a55 Update greetings workflow message 2025-01-30 21:48:14 +00:00
Mahesh Ravariya
5d77d36236 Updated correct tag for more resources (#8142)
More resources had wrong tag "@articles". Changed it to "@course"
2025-01-30 21:44:27 +00:00
Sergio V
37b3140516 Fix typo (#8141) 2025-01-30 21:40:34 +00:00
Ed Lan
34b68478cc Add guide link (#8143) 2025-01-30 21:40:11 +00:00
Ed Lan
9bb86408c5 Add link to ai data scientist guide (#8144) 2025-01-30 21:39:56 +00:00
Kamran Ahmed
d07fd3d183 Update pricing section 2025-01-30 21:39:29 +00:00
Kamran Ahmed
9aa363a01e Add java vs javascript guide 2025-01-30 18:57:33 +00:00
Kamran Ahmed
9f2a33f078 Add full-stack questions 2025-01-30 12:31:39 +00:00
Kamran Ahmed
91cfa88b3d Fix broken course url 2025-01-29 19:31:43 +00:00
Kamran Ahmed
d87ea1c972 Add full-stack questions 2025-01-29 15:49:48 +00:00
Kamran Ahmed
dbf2353a41 Add fullstack questions 2025-01-29 15:49:48 +00:00
Vedansh
8d78c17c77 fix: update nodejs exit topic links 2025-01-29 19:10:54 +06:00
github-actions[bot]
698dbbd7d8 chore: update roadmap content json (#8135)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-29 19:08:30 +06:00
Kamran Ahmed
25216d4052 Add data scientist lifecycle guid 2025-01-29 12:17:17 +00:00
Kamran Ahmed
2be0d61a1e Course cache clearing 2025-01-29 00:17:06 +00:00
Kamran Ahmed
f3ee75e92d Add ai data science tools guide 2025-01-28 22:15:19 +00:00
Kamran Ahmed
db0b5e77ad Add guide for data science skills 2025-01-28 18:13:10 +00:00
Kamran Ahmed
c239886049 Add guide for data science career path 2025-01-28 17:12:07 +00:00
Meher
aab03074f8 Add authorization types link (#8130)
Link to API Authentication types was provided instead of authorization types.
2025-01-28 11:42:39 +00:00
Rikuya Osawa
433302b910 fix: resource link on Outliers (#8126) 2025-01-28 07:58:12 +06:00
Alice
a583937f5c fix: file name typo 2025-01-28 07:57:39 +06:00
Arik Chakma
412e3b5935 feat: add subscribe to changelog (#8012)
* feat: add subscribe to changelog

* Add subscribe to changelog

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2025-01-27 23:23:19 +00:00
Arik Chakma
1ffa292c98 feat: new user flag (#8070)
* feat: new user flag

* feat: share icon event

* fix: upload the query tag

* fix: name and label

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
2025-01-27 22:00:24 +00:00
Kamran Ahmed
0bef28fa20 Update flag issue 2025-01-27 16:13:23 +00:00
github-actions[bot]
1af013d5f8 chore: update roadmap content json (#8121)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-27 22:12:16 +06:00
Kamran Ahmed
28af19cd1c Add SQL course landing page (#8127)
* wip: courses

* fix: update course sidebar

* wip

* fix: merge lessons

* wip

* wip: course footer

* wip

* fix: refactor layout

* fix: refactor

* feat: course progress

* fix: update current lesson store

* fix: refactor props

* wip

* wip

* feat: course certificate

* wip: course rating

* wip: course notes

* wip

* feat: implement course notes

* feat: make card clickable

* fix: add hover background

* fix: refactor course layout

* fix: resizeable

* fix: go back on save

* feat: delete confimation

* wip

* feat: chat UI

* fix: lesson complete guard issue

* wip: add public json files

* wip: course ai

* fix: loading card

* Fix failing dev without internet

* Light mode and UI changes

* Update UI

* Update course UI

* Add chapter page

* Improve sidebar of course

* Update navigation:

* Update quiz view

* Improve UI for quiz attempts

* Remove unnecessary console.logs

* Add progress loading skeletons

* Update UI

* Change background color of editor

* Fix line color not applied on editor

* UI updates

* feat: empty view

* feat: course ai token limit

* feat: handle auth users

* wip

* feat: course landing page

* wip

* Add first chapter of SQL

* Add introduction chapter

* Add quiz for introduction

* Add expressions in select

* Add content for DISTINCT

* Add filter with where

* Add lesson about limit and offset

* Add lesson for handling null values

* Add lesson about comments

* Add challenges

* Add challenge

* Add challenge

* Add challenge

* Add challenge 7

* Add creating tables lesson

* Add common data types lesson

* Add data types in sqlite

* Add more on data types lesson

* feat: course landing page

* Add more on numeric types

* Update

* Add lesson about temporal data types

* Add constraints

* Add primary keys chapter

* Add modifying tables

* Add dropping and truncating

* Rewrite for PostgreSQL

* Update numeric types to PostgreSQL

* Improve temporal data type content

* Improve temporal data type content

* Add setup for temporal data

* Improve challenges in SQL basics

* Update challenge names

* Add new challenges

* Add temporal validation challenge

* Add new constraint

* Add modifying tables query

* Removing table

* Add insert operations lesson

* Add updating data lesson

* Add delete operations

* Add inserting and updating challenges

* Add lesson for cleaning up data

* Update course title

* Add relation data lesson

* Add relationships and types

* Add relationships and types

* wip

* Add joins lesson

* Joins in queries

* Add inner join details

* Add join queries

* Add inner join details

* Add foreign key constraint lesson

* Update composite foreign keys

* Add lesson about foreign keys

* Add lesson about set operation queries

* Add lesson about set operation queries

* Add set operator challenges

* Add new challenge

* Add view lesson

* Add notes in views

* Add inactive customer challenge

* Add high value order challenge

* gst

* Add new challenges

* Add readers like you challenge

* Update inactive customer query

* Update inactive customer query

* Update inactive customer query

* Update inactive customer query

* Update inactive customer query

* add challenge for same price books

* Add aggregate functions introduction

* Add basic aggregation lesson

* Add basic aggregation lesson

* Add introduction quiz

* Add grouping lesson

* Add grouping gotchas

* Add grouping and filtering lesson

* Add note for lesson

* Add challenges for aggregate

* Update aggregate challenge

* Rearrange chapters

* Add scalar functions lessons

* Add numeric functions

* Add date functions

* Add conversion functions

* Add conversion functions

* Add logical functions chapter

* Add exercises

* Add new challenges

* Add monthly sales analysis

* Add subqueries and ctes

* Update

* Add correlated subqueries

* Add common table expressions

* Add common-table expressions

* Add example

* Add recursive CTEs

* Add subquery challenge

* Add latest category books challenge

* Add challenges

* Add bestseller rankings challenge

* Add new customer analysis

* Add daily sales report

* Improve queries

* Add introduction to window functions

* Add over and partition

* wip: billing page

* Add ranking functions

* Improve ranking functions

* Add order by

* Add window frames lesson

* Add window frames explanation

* Add challenges for window functions

* Add price range analysis challenge

* wip

* wip: course enroll

* fix: start learning

* wip

* wip

* Enrollment changes

* wip

* wip

* feat: mobile responsive

* Changelog banner refactor

* Update

* Header for course

* Header for what to expect

* UI color

* Table of contents

* Icons on chapters

* Change design for road to sql

* Add sql course page

* Add lesson content

* Update UI

* Expanded chapter row

* Add course page

* Refactor

* Add spotlight

* Improve features

* Add course features

* Add certificate note

* Zoom in on the image

* Update

* Add floating purchase

* Floating purchase indicatorg

* Add about section

* Update about section

* Add FAQ section

* Update UI

* Add purchase power parity

* Show purchasing power pricing

* Add course login popup

* Add course login popup

* Add account button

* Add trigger for course purchase

* Course purchase param

* Buy button changes

* Add faqs

* Add purchase trigger on reload

* Landing verification

* Make header responsive

* Make course page upper half responsive

* Full page is responsive

* Fix login height bug

* Responsiveness

* Implement login after checkout

* Remove unused code

* Update dependenciesg

* Update

* fix: refetch mount to false

* Remove unused code

* Remove unused code

* Remove unused code

* Remove unused code

* Remove unused code

* Remove unused code

* Remove unused

* Add quizzes to chapters

* Update course slug

* Update dependencies

* Add header for sql course

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-27 15:32:13 +00:00
Ed Lan
4696af9c6a feat: update topic content 2025-01-27 00:45:23 +06:00
Ed Lan
e2dece0e6a feat: update topic content 2025-01-27 00:45:07 +06:00
Ed Lan
a5f483c335 feat: update topic content 2025-01-27 00:44:50 +06:00
Vedansh
4552d3f9c8 refractor: improve data analyst roadmap (#8104)
* refractor 36 topics

* refractor remaining topics - 16
2025-01-27 00:44:14 +06:00
Rikuya Osawa
f213bd9604 fix: inappropriate resource tags (#8112) 2025-01-27 00:38:58 +06:00
ZIT2050
de65909357 fix: replace topic resource
* Update csrf-protection@J9yIXZTtwbFzH2u4dI1ep.md

@official@Security Guide page not found 404

* Update src/data/roadmaps/php/content/csrf-protection@J9yIXZTtwbFzH2u4dI1ep.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-27 00:38:07 +06:00
niibuthelezi
9a77ca8a17 feat: add topic resource 2025-01-27 00:35:27 +06:00
github-actions[bot]
7ab3f758fd chore: update roadmap content json (#8096)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-26 06:33:16 +06:00
gavhu10
bcf4126b3a fix: add a tuple resource (#8089) 2025-01-22 22:39:33 +06:00
gavhu10
f9dbd16afe feat: add resource for dictionaries (#8090) 2025-01-22 22:10:31 +06:00
cepjant
21ed4a647f fix: update topic content 2025-01-22 22:09:44 +06:00
github-actions[bot]
68cf2c00c6 chore: update roadmap content json (#8092)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-22 22:05:55 +06:00
Michal Freriks
d3b0c32eb7 fix: topic content
The statement was not true, as technically, global variables are also able to be declared
2025-01-21 23:22:50 +06:00
Karim Safan
37ca08f956 feat: add topic content resources
* Update dml@tcQSH-eAvJUZuePTDjAIb.md

adding two resource for DML

* Update src/data/roadmaps/computer-science/content/dml@tcQSH-eAvJUZuePTDjAIb.md

* Update src/data/roadmaps/computer-science/content/dml@tcQSH-eAvJUZuePTDjAIb.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-21 23:17:52 +06:00
Said-BT
b40ec83137 fix: add topic content resources
* Update tuples@i7xIGiXU-k5UIKHIhQPjE.md

added 2 resources as this section has nothing.

* Update tuples@i7xIGiXU-k5UIKHIhQPjE.md

* Update src/data/roadmaps/python/content/tuples@i7xIGiXU-k5UIKHIhQPjE.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-21 23:13:34 +06:00
psychobioMACHINE
edc7de8226 feat: add topic content resource
Added "freeCodeCamp | Responsive Web Design Certification" to HTML and CSS. [url: "

https://www.freecodecamp.org/learn/2022/responsive-web-design/

"]
It is a free certification with step-by-step guided projects in HTML & CSS.
The final project is even your first webpage portfolio.
2025-01-21 23:12:14 +06:00
psychobioMACHINE
5bffdebeb8 feat: add topic content resource
Added "freeCodeCamp | Responsive Web Design Certification" to HTML and CSS. [url: "

https://www.freecodecamp.org/learn/2022/responsive-web-design/

"]
It is a free certification with step-by-step guided projects in HTML & CSS.
The final project is even your first webpage portfolio.
2025-01-21 23:11:36 +06:00
Jawher Kl
e5e902a268 fix: topic content resource 2025-01-21 23:03:07 +06:00
Milen Todev
5842b0a692 fix: typo in backend-beginner (#8002)
This pull request corrects a typo in the instructions on the right-hand side. The word "knowledge" was incorrectly spelled as "konwledge."

Changes:
Corrected the typo from "konwledge" to "knowledge" in the instructions.
2025-01-21 22:59:16 +06:00
mrlane51
f1592571db feat: add topic content resources
Added a useful detailed video of Resource Management that is easily digestible.
2025-01-21 22:54:30 +06:00
xaviduds
4058dff406 fix: grammar typo Priviliges -> Privileges (#8016)
Commits done in PostgreSQL Roadmap
2025-01-21 22:35:15 +06:00
Abhay Shukla
5a813eea04 feat: add topic content resources
* Update how-computers-calculate@GDLKJkKgB-i7n0YcV2NDa.md

I have added short description so that learner should not roam and can get overview of the topic.

* Update src/data/roadmaps/computer-science/content/how-computers-calculate@GDLKJkKgB-i7n0YcV2NDa.md

---------

Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-21 22:16:18 +06:00
Shivam Kumar
e577ba095f feat: add topic content resource
Added Design Guidelines to the title and resources from IxD Foundation regarding User Interface design guidelines.
2025-01-21 22:12:17 +06:00
Attila Maraz
069cb6fc06 fix: topic content resources 2025-01-21 22:01:50 +06:00
Priyanshu Maurya
b0a3cdc8c4 fix: add topic content resource
* Update 101-docker-compose.md

* Added type (Update 101-docker-compose.md)
2025-01-21 22:00:08 +06:00
Priyanshu Maurya
536a79ef59 fix: topic content resources
previous link

https://www.docker.com/blog/docker-cli-commands-cheat-sheet/

has been expired 

I replaced it with this 

https://docs.docker.com/get-started/docker_cheatsheet.pdf
2025-01-21 21:58:16 +06:00
Anshuman Singh
5a132b6f07 fix: typo mistake 'Fareness' changed to 'Fairness' #8041 (#8043)
Co-authored-by: Arik Chakma <arikchangma@gmail.com>
2025-01-21 21:31:29 +06:00
任跃兵
e3b56ab716 fix: topic content resources 2025-01-21 21:29:04 +06:00
任跃兵
2d8030a175 fix: topic content resources 2025-01-21 21:28:33 +06:00
Rikuya Osawa
dca2ba5fac feat: add resource for linux service management (#8053)
Added hands-on resource for Linux service management. LabEx is an excellent guide which explores Linux service management through systemd and systemctl.
2025-01-21 21:27:19 +06:00
LaralenaV
16fc4b08ff fix: add top content resources
Added resources: article and video
2025-01-21 21:26:24 +06:00
Fuchen Shi
7a7590d872 fix: incorrect descriptions of the P = NP problem (#8084) 2025-01-21 21:23:56 +06:00
Glitchagon
073708e7e9 fix: resource content grammar
Fixed a couple spelling errors in the Use Delimiters section of the Prompt Engineering course.
2025-01-21 21:22:22 +06:00
Glitchagon
66a8c2e73c fix: resource content grammar
Fixed grammar and spelling errors in the Types of LLM's page of Prompt Engineering course.
2025-01-21 20:55:39 +06:00
github-actions[bot]
34425019b7 chore: update roadmap content json (#8083)
Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com>
2025-01-21 20:54:48 +06:00
Arik Chakma
699e05a3b4 fix: user progress modal percentage (#8082) 2025-01-20 17:44:41 +00:00
Kamran Ahmed
3bd379692f Add AI roadmap link 2025-01-20 17:27:38 +00:00
996 changed files with 30463 additions and 38720 deletions

View File

@@ -3,6 +3,6 @@
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1737069970237
"lastUpdateCheck": 1741697790683
}
}

View File

@@ -1,3 +1,10 @@
PUBLIC_API_URL=https://api.roadmap.sh
PUBLIC_AVATAR_BASE_URL=https://dodrc8eu8m09s.cloudfront.net/avatars
PUBLIC_EDITOR_APP_URL=https://draw.roadmap.sh
PUBLIC_EDITOR_APP_URL=https://draw.roadmap.sh
PUBLIC_COURSE_APP_URL=http://localhost:5173
PUBLIC_STRIPE_INDIVIDUAL_MONTHLY_PRICE_ID=
PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_ID=
PUBLIC_STRIPE_INDIVIDUAL_MONTHLY_PRICE_AMOUNT=10
PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_AMOUNT=100

View File

@@ -13,4 +13,4 @@ jobs:
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'
-d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront,cloudfront-course", "is_verbose": false } }'

View File

@@ -15,19 +15,9 @@ jobs:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
🙌 Hello! Thank you for taking the time to file an issue.
If this is a bug report, please include any relevant logs or details that can help us debug the problem. Your help is greatly appreciated! 💡
We'll get back to you as soon as possible, kindly be patient for a response from a maintainer.
pr-message: |
🎉 Warm regards and welcome! Thank you for your first ever contribution to **Roadmap.sh**!
Thank you for your first ever contribution to [roadmap.sh](https://roadmap.sh)! 🎉
We appreciate your effort and enthusiasm. Before diving in, we kindly ask you to take a moment to go through our [Contribution Guidelines](https://github.com/kamranahmedse/developer-roadmap/blob/master/contributing.md) 📘 to ensure your contribution aligns with the project's standards and goals.
Please make sure to follow the [contribution guidelines](https://github.com/kamranahmedse/developer-roadmap/blob/master/contributing.md) when contributing to this project. Any PRs that don't follow the guidelines will be closed.
If you are fixing a bug, please reference the associated issue number in your pull request description. 🐛
If you're working on a new feature, feel free to check with the community on [discord](https://roadmap.sh/discord) to ensure the feature will be accepted. *Also, kindly refrain pinging the maintainer(s).* 🚀
Thanks for choosing to contribute, and for making this project better! 🌟
Thanks for choosing to contribute, and for helping make this project better! 🌟

View File

@@ -34,6 +34,7 @@
"@astrojs/sitemap": "^3.2.0",
"@astrojs/tailwind": "^5.1.2",
"@fingerprintjs/fingerprintjs": "^4.5.0",
"@microsoft/clarity": "^1.0.0",
"@nanostores/react": "^0.8.0",
"@napi-rs/image": "^1.9.2",
"@resvg/resvg-js": "^2.6.2",
@@ -52,6 +53,7 @@
"js-cookie": "^3.0.5",
"lucide-react": "^0.452.0",
"luxon": "^3.5.0",
"markdown-it-async": "^2.0.0",
"nanoid": "^5.0.7",
"nanostores": "^0.11.3",
"node-html-parser": "^6.1.13",
@@ -62,22 +64,27 @@
"react-calendar-heatmap": "^1.9.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.3.1",
"react-textarea-autosize": "^8.5.7",
"react-tooltip": "^5.28.0",
"reactflow": "^11.11.4",
"rehype-external-links": "^3.0.0",
"remark-parse": "^11.0.0",
"roadmap-renderer": "^1.0.6",
"sanitize-html": "^2.13.1",
"satori": "^0.11.2",
"satori-html": "^0.3.2",
"sharp": "^0.33.5",
"shiki": "^3.1.0",
"slugify": "^1.6.6",
"tailwind-merge": "^2.5.3",
"tailwindcss": "^3.4.13",
"tiptap-markdown": "^0.8.10",
"turndown": "^7.2.0",
"unified": "^11.0.5",
"zustand": "^4.5.5"
},
"devDependencies": {
"@ai-sdk/google": "^1.1.19",
"@playwright/test": "^1.48.0",
"@tailwindcss/typography": "^0.5.15",
"@types/dom-to-image": "^2.6.7",
@@ -86,7 +93,9 @@
"@types/prismjs": "^1.26.4",
"@types/react-calendar-heatmap": "^1.6.7",
"@types/react-slick": "^0.23.13",
"@types/sanitize-html": "^2.13.0",
"@types/turndown": "^5.0.5",
"ai": "^4.1.51",
"csv-parser": "^3.0.0",
"gh-pages": "^6.2.0",
"js-yaml": "^4.1.0",

3072
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -42,7 +42,7 @@
]
},
"mwPJh33MEUQ4Co_LiVEOb": {
"title": "Differential Calculus",
"title": "Differential Calculus ",
"description": "",
"links": [
{
@@ -330,17 +330,17 @@
"links": [
{
"title": "Advantages and Disadvantages of AI",
"url": "https://towardsdatascience.com/advantages-and-disadvantages-of-artificial-intelligence-182a5ef6588c",
"url": "https://medium.com/@laners.org/advantages-and-disadvantages-of-artificial-intelligence-cd6e42819b20",
"type": "article"
},
{
"title": "Reinforcement Learning 101",
"url": "https://towardsdatascience.com/reinforcement-learning-101-e24b50e1d292",
"url": "https://medium.com/towards-data-science/reinforcement-learning-101-e24b50e1d292",
"type": "article"
},
{
"title": "Understanding AUC-ROC Curve",
"url": "https://towardsdatascience.com/understanding-auc-roc-curve-68b2303cc9c5",
"url": "https://medium.com/towards-data-science/understanding-auc-roc-curve-68b2303cc9c5",
"type": "article"
}
]

View File

@@ -632,10 +632,26 @@
}
]
},
"bias-and-fareness@lhIU0ulpvDAn1Xc3ooYz_.md": {
"title": "Bias and Fareness",
"description": "",
"links": []
"lhIU0ulpvDAn1Xc3ooYz_": {
"title": "Bias and Fairness",
"description": "Bias and fairness in AI refer to the challenges of ensuring that machine learning models do not produce discriminatory or skewed outcomes. Bias can arise from imbalanced training data, flawed assumptions, or biased algorithms, leading to unfair treatment of certain groups based on race, gender, or other factors. Fairness aims to address these issues by developing techniques to detect, mitigate, and prevent biases in AI systems. Ensuring fairness involves improving data diversity, applying fairness constraints during model training, and continuously monitoring models in production to avoid unintended consequences, promoting ethical and equitable AI use.\n\nLearn more from the following resources:",
"links": [
{
"title": "What Do We Do About the Biases in AI?",
"url": "https://hbr.org/2019/10/what-do-we-do-about-the-biases-in-ai",
"type": "article"
},
{
"title": "AI Bias - What Is It and How to Avoid It?",
"url": "https://levity.ai/blog/ai-bias-how-to-avoid",
"type": "article"
},
{
"title": "What about fairness, bias and discrimination?",
"url": "https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/artificial-intelligence/guidance-on-ai-and-data-protection/how-do-we-ensure-fairness-in-ai/what-about-fairness-bias-and-discrimination/",
"type": "article"
}
]
},
"sWBT-j2cRuFqRFYtV_5TK": {
"title": "Security and Privacy Concerns",

View File

@@ -458,8 +458,29 @@
},
"O7wjldZ3yTA2s_F-UnJw_": {
"title": "Rate Limiting",
"description": "Rate Limiting is a critical aspect of API Design that dictates the number of API calls a client can make within a specified timeframe. This helps in managing resource allocation, preventing abuse of the API, and maintaining the overall health of the API system. Proper rate limiting measures should be in place to ensure the API's stability, thereby delivering a consistent and reliable service to all consumers. It works primarily by setting a limit on the frequency of client requests, thereby preventing individual users from overloading the system. It is crucial to design and implement rate limiting carefully for maintaining API availability and performance.",
"links": []
"description": "Rate Limiting is a critical aspect of API Design that dictates the number of API calls a client can make within a specified timeframe. This helps in managing resource allocation, preventing abuse of the API, and maintaining the overall health of the API system. Proper rate limiting measures should be in place to ensure the API's stability, thereby delivering a consistent and reliable service to all consumers. It works primarily by setting a limit on the frequency of client requests, thereby preventing individual users from overloading the system. It is crucial to design and implement rate limiting carefully for maintaining API availability and performance.\n\nLearn more from the following resources:",
"links": [
{
"title": "Rate limit",
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Rate_limit",
"type": "article"
},
{
"title": "Throttle",
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Throttle",
"type": "article"
},
{
"title": "Debounce",
"url": "https://developer.mozilla.org/en-US/docs/Glossary/Debounce",
"type": "article"
},
{
"title": "What is rate limiting? | Rate limiting and bots",
"url": "https://www.cloudflare.com/en-gb/learning/bots/what-is-rate-limiting/",
"type": "article"
}
]
},
"20KEgZH6cu_UokqWpV-9I": {
"title": "Idempotency",
@@ -569,6 +590,11 @@
"title": "Caching REST API Response",
"url": "https://restfulapi.net/caching/",
"type": "article"
},
{
"title": "HTTP caching",
"url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching",
"type": "article"
}
]
},
@@ -709,7 +735,7 @@
"links": [
{
"title": "API Authorization Methods",
"url": "https://konghq.com/blog/engineering/common-api-authentication-methods",
"url": "https://www.pingidentity.com/en/resources/identity-fundamentals/authorization/authorization-methods.html",
"type": "article"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -629,7 +629,7 @@
"type": "article"
},
{
"title": "MySQL Full Course for free",
"title": "MySQL Complete Course",
"url": "https://www.youtube.com/watch?v=5OdVJbNCSso",
"type": "video"
}
@@ -1690,8 +1690,8 @@
"type": "article"
},
{
"title": "Architectural Patterns in a nutshell",
"url": "https://towardsdatascience.com/10-common-software-architectural-patterns-in-a-nutshell-a0b47a1e9013",
"title": "10 Common Software Architectural Patterns in a nutshell",
"url": "https://theiotacademy.medium.com/10-common-software-architectural-patterns-in-a-nutshell-1b1f6cf5036b",
"type": "article"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -55,12 +55,12 @@
"type": "article"
},
{
"title": "Python Website",
"title": "Python",
"url": "https://www.python.org/",
"type": "article"
},
{
"title": "Python Getting Started",
"title": "Getting Started with Python",
"url": "https://www.python.org/about/gettingstarted/",
"type": "article"
},
@@ -182,6 +182,11 @@
"title": "Rust",
"description": "Rust is a modern systems programming language focusing on safety, speed, and concurrency. It accomplishes these goals by being memory safe without using garbage collection.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Visit Dedicated Rust Roadmap",
"url": "https://roadmap.sh/rust",
"type": "article"
},
{
"title": "The Rust Programming Language - online book",
"url": "https://doc.rust-lang.org/book/",
@@ -213,6 +218,11 @@
"title": "C++",
"description": "C++ is a powerful general-purpose programming language. It can be used to develop operating systems, browsers, games, and so on. C++ supports different ways of programming like procedural, object-oriented, functional, and so on. This makes C++ powerful as well as flexible.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "C++ Roadmap",
"url": "https://roadmap.sh/cpp",
"type": "article"
},
{
"title": "Learn C++",
"url": "https://learncpp.com/",
@@ -238,11 +248,6 @@
"url": "https://www.w3schools.com/cpp/default.asp",
"type": "article"
},
{
"title": "C++ Roadmap",
"url": "https://roadmap.sh/cpp",
"type": "article"
},
{
"title": "Explore top posts about C++ Programming",
"url": "https://app.daily.dev/tags/c++?ref=roadmapsh",
@@ -326,6 +331,11 @@
"url": "https://techdevguide.withgoogle.com/paths/data-structures-and-algorithms/",
"type": "course"
},
{
"title": "Visit Dedicated DSA Roadmap",
"url": "https://roadmap.sh/datastructures-and-algorithms",
"type": "article"
},
{
"title": "Data Structures and Algorithms",
"url": "https://www.javatpoint.com/data-structure-tutorial",
@@ -645,6 +655,11 @@
"title": "Complete Binary Tree - Programiz",
"url": "https://www.programiz.com/dsa/complete-binary-tree",
"type": "article"
},
{
"title": "Complete Binary Trees",
"url": "https://www.wscubetech.com/resources/dsa/complete-binary-tree",
"type": "article"
}
]
},
@@ -771,8 +786,14 @@
},
"HZ1kk0TQ13FLC9t13BZl5": {
"title": "Adjacency Matrix",
"description": "",
"links": []
"description": "An adjacency matrix is a square matrix used to represent a finite graph. It is used to represent the connections between vertices in a graph. The matrix is filled with 0s and 1s, where a 1 represents a connection between two vertices and a 0 represents no connection.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Adjacency Matrix",
"url": "https://en.wikipedia.org/wiki/Adjacency_matrix",
"type": "article"
}
]
},
"rTnKJcPniUtqvfOyC88N0": {
"title": "Adjacency List",
@@ -1017,8 +1038,14 @@
},
"7a6-AnBI-3tAU1dkOvPkx": {
"title": "Common Algorithms",
"description": "Here are some common algorithms that you should know. You can find more information about them in the [Algorithms](https://www.khanacademy.org/computing/computer-science/algorithms) section of the Computer Science course.\n\n* Sorting\n* Recursion\n* Searching\n* Cache Algorithms\n* Tree Algorithms\n* Graph Algorithms\n* Greedy Algorithms\n* Backtracking\n* Substring Search\n* Suffix Arrays\n* Dynamic Programming",
"links": []
"description": "Here are some common algorithms that you should know. You can find more information about them in the Algorithms section of the Computer Science course.\n\n* Sorting\n* Recursion\n* Searching\n* Cache Algorithms\n* Tree Algorithms\n* Graph Algorithms\n* Greedy Algorithms\n* Backtracking\n* Substring Search\n* Suffix Arrays\n* Dynamic Programming\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Top Algorithms and Data Structures",
"url": "https://towardsdatascience.com/top-algorithms-and-data-structures-you-really-need-to-know-ab9a2a91c7b5",
"type": "article"
}
]
},
"0_qNhprnXU3i8koW3XTdD": {
"title": "Tail Recursion",
@@ -1143,20 +1170,26 @@
"XwyqBK9rgP1MMcJrdIzm5": {
"title": "Linear Search",
"description": "Linear search is a very simple algorithm that is used to search for a value in an array. It sequentially checks each element of the array until a match is found or until all the elements have been searched.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "Linear Search",
"url": "https://www.programiz.com/dsa/linear-search",
"type": "article"
}
]
},
"4wGBYFZpcdTt97WTbSazx": {
"title": "Bubble Sort",
"description": "Bubble sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Bubble Sort",
"url": "https://www.youtube.com/watch?v=P00xJgWzz2c&index=1&list=PL89B61F78B552C1AB",
"type": "video"
"title": "Bubble Sort Algorithm",
"url": "https://www.programiz.com/dsa/bubble-sort",
"type": "article"
},
{
"title": "Analyzing Bubble Sort",
"url": "https://www.youtube.com/watch?v=ni_zk257Nqo&index=7&list=PL89B61F78B552C1AB",
"title": "Bubble Sort",
"url": "https://www.youtube.com/watch?v=P00xJgWzz2c&index=1&list=PL89B61F78B552C1AB",
"type": "video"
},
{
@@ -1375,7 +1408,13 @@
"Yf5gOIe7oiL19MjEVcpdw": {
"title": "Depth First Search",
"description": "Depth first search is a graph traversal algorithm that starts at a root node and explores as far as possible along each branch before backtracking.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "Depth-first Search",
"url": "https://en.wikipedia.org/wiki/Depth-first_search",
"type": "article"
}
]
},
"eY4nK2lPYsrR-a_8y2sao": {
"title": "Bellman Ford's Algorithm",
@@ -1442,8 +1481,13 @@
},
"aBjBHpq_OajgQjxdCobXD": {
"title": "Finding Hamiltonian Paths",
"description": "Hamiltonian paths are paths that visit every node in a graph exactly once. They are named after the famous mathematician [Hamilton](https://en.wikipedia.org/wiki/William_Rowan_Hamilton). Hamiltonian paths are a special case of [Hamiltonian cycles](https://en.wikipedia.org/wiki/Hamiltonian_cycle), which are cycles that visit every node in a graph exactly once.\n\nVisit the following resources to learn more:",
"description": "Hamiltonian paths are paths that visit every node in a graph exactly once. They are named after the famous mathematician Hamilton. Hamiltonian paths are a special case of Hamiltonian cycles, which are cycles that visit every node in a graph exactly once.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Hamiltonian Cycles",
"url": "https://en.wikipedia.org/wiki/Hamiltonian_cycle",
"type": "article"
},
{
"title": "Hamiltonian Path",
"url": "https://www.hackerearth.com/practice/algorithms/graphs/hamiltonian-path/tutorial/",
@@ -1967,13 +2011,13 @@
"description": "Class Diagrams are used to model the static structure of a system. They are used to show the classes, their attributes, operations (or methods), and the relationships between objects.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "UML Class Diagram Tutorial",
"url": "https://www.youtube.com/watch?v=UI6lqHOVHic",
"type": "video"
"title": "Class Diagrams",
"url": "https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/",
"type": "article"
},
{
"title": "UML Class Diagram Tutorial",
"url": "https://www.youtube.com/watch?v=3cmzqZzwNDM&list=PLfoY2ARMh0hC2FcJKP5voAKCpk6PZXSd5&index=2",
"url": "https://www.youtube.com/watch?v=UI6lqHOVHic",
"type": "video"
}
]
@@ -2354,26 +2398,6 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},
@@ -2425,26 +2449,6 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},
@@ -2496,26 +2500,6 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},
@@ -2567,26 +2551,6 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},
@@ -2643,32 +2607,12 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},
"0btHNkzWL1w_-pUgU_k2y": {
"title": "P = NP",
"description": "The P = NP problem is one of the most famous problems in computer science. It asks if the problem of determining if a given input belongs to a certain class of problems is as hard as the problem of solving the given input. In other words, it asks if the problem of determining if a given input belongs to a certain class of problems is as hard as the problem of determining if a given input belongs to a certain class of problems. This problem is also known as the Halting Problem.\n\nVisit the following resources to learn more:",
"description": "The P = NP problem is one of the most famous problems in computer science. It asks whether a problem that can be solved in polynomial time on a non-deterministic machine (i.e., the problem is in NP) can also be solved in polynomial time on a deterministic machine (i.e., the problem is in P).\n\nIf you can find a polynomial-time solution to an NP-complete problem, then all problems in NP can be solved in polynomial time. This shows that P = NP.\n\nIf you can prove for any single NP-complete problem that it is only solvable in exponential time, then all NP-complete problems are only solvable in exponential time. This shows that P ≠ NP.\n\nSo far, we don't know whether P = NP or P ≠ NP.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Whats P=NP?, and why is it such a famous question?",
@@ -2941,11 +2885,6 @@
"url": "https://www.youtube.com/watch?v=svfnVhJOfMc&index=8&list=PLA5Lqm4uh9Bbq-E0ZnqTIa8LRaL77ica6",
"type": "video"
},
{
"title": "MIT 6.851 - Memory Hierarchy Models",
"url": "https://www.youtube.com/watch?v=V3omVLzI0WE&index=7&list=PLUl4u3cNGP61hsJNdULdudlRL493b-XZf",
"type": "video"
},
{
"title": "B-Trees (playlist) in 26 minutes",
"url": "https://www.youtube.com/playlist?list=PL9xmBV_5YoZNFPPv98DjTdD9X6UI9KMHz",
@@ -3057,6 +2996,11 @@
"title": "CDN",
"description": "A CDN is a network of servers that are distributed geographically. The servers are connected to each other and to the internet. The servers are used to deliver content to users. The content is delivered to the user from the server that is closest to the user. This is done to reduce latency and improve the performance of the content delivery.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is a CDN?",
"url": "https://www.cloudflare.com/learning/cdn/what-is-a-cdn/",
"type": "article"
},
{
"title": "Content Delivery Network (CDN) - System Design",
"url": "https://dev.to/karanpratapsingh/system-design-the-complete-course-10fo#content-delivery-network-cdn",
@@ -3163,6 +3107,11 @@
"title": "GraphQL",
"description": "GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Visit Dedicated GraphQL Roadmap",
"url": "https://roadmap.sh/graphql",
"type": "article"
},
{
"title": "Apollo GraphQL Tutorials",
"url": "https://www.apollographql.com/tutorials/",
@@ -3227,7 +3176,7 @@
"description": "Long polling is a technique used to implement server push functionality over HTTP. It is a method of opening a request on the server and keeping it open until an event occurs, at which point the server responds. This is in contrast to a regular HTTP request, where the server responds immediately with whatever data is available at the time.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Long polling",
"title": "Long Polling",
"url": "https://javascript.info/long-polling",
"type": "article"
},
@@ -3252,7 +3201,18 @@
"bVjI14VismTHNCyA0mEBP": {
"title": "Web Sockets",
"description": "Web sockets are a bidirectional communication protocol between a client and a server. They are used for real-time applications like chat, multiplayer games, and live data updates. Web sockets are also used to establish a connection between a server and a client. This connection is then used to send data in both directions.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "WebSockets",
"url": "https://en.wikipedia.org/wiki/WebSocket",
"type": "article"
},
{
"title": "Web Sockets API",
"url": "https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API",
"type": "article"
}
]
},
"JckRqZA8C6IqQLPpTCgf4": {
"title": "SSE",
@@ -3280,12 +3240,12 @@
"description": "A database is a collection of useful data of one or more related organizations structured in a way to make data an asset to the organization. A database management system is a software designed to assist in maintaining and extracting large collections of data in a timely fashion.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Oracle: What is a Database?",
"title": "What is a Database?",
"url": "https://www.oracle.com/database/what-is-database/",
"type": "article"
},
{
"title": "Prisma.io: What are Databases?",
"title": "What are Databases?",
"url": "https://www.prisma.io/dataguide/intro/what-are-databases",
"type": "article"
},
@@ -3362,22 +3322,51 @@
"ii1vF74u3yrFNlw_21b3B": {
"title": "DDL",
"description": "DDL or Data Definition Language actually consists of the SQL commands that can be used to define the database schema. It simply deals with descriptions of the database schema and is used to create and modify the structure of database objects in the database. DDL is a set of SQL commands used to create, modify, and delete database structures but not data. These commands are normally not used by a general user, who should be accessing the database via an application.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "DDL",
"url": "https://en.wikipedia.org/wiki/Data_definition_language",
"type": "article"
}
]
},
"tcQSH-eAvJUZuePTDjAIb": {
"title": "DML",
"description": "The SQL commands that deals with the manipulation of data present in the database belong to DML or Data Manipulation Language and this includes most of the SQL statements. It is the component of the SQL statement that controls access to data and to the database. Basically, DCL statements are grouped with DML statements.\n\nVisit the following resources to learn more:",
"links": []
"description": "The SQL commands that manipulate data in the database belong to DML, or Data Manipulation Language, and this includes most of the SQL statements. DCL is the component of the SQL statement that controls access to data and to the database. Basically, DCL statements are grouped with DML statements.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "DML: Data Manipulation Language",
"url": "https://satoricyber.com/glossary/dml-data-manipulation-language",
"type": "article"
},
{
"title": "Difference Between DDL and DML",
"url": "https://appmaster.io/blog/difference-between-ddl-and-dml",
"type": "article"
}
]
},
"05lkb3B86Won7Rkf-8DeD": {
"title": "DQL",
"description": "DQL statements are used for performing queries on the data within schema objects. The purpose of the DQL Command is to get some schema relation based on the query passed to it. We can define DQL as follows it is a component of SQL statement that allows getting data from the database and imposing order upon it. It includes the SELECT statement. This command allows getting the data out of the database to perform operations with it. When a SELECT is fired against a table or tables the result is compiled into a further temporary table, which is displayed or perhaps received by the program i.e. a front-end.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "Data Query Language",
"url": "https://en.wikipedia.org/wiki/Data_query_language",
"type": "article"
}
]
},
"4bUmfuP2qgcli8I2Vm9zh": {
"title": "DCL",
"description": "DCL includes commands such as GRANT and REVOKE which mainly deal with the rights, permissions, and other controls of the database system.\n\nVisit the following resources to learn more:",
"links": []
"links": [
{
"title": "DCL",
"url": "https://en.wikipedia.org/wiki/Data_Control_Language",
"type": "article"
}
]
},
"_sm63rZNKoibVndeNgOpW": {
"title": "Locking",
@@ -3423,7 +3412,7 @@
},
"q3nRhTYS5wg9tYnQe2sCF": {
"title": "BASE",
"description": "The rise in popularity of NoSQL databases provided a flexible and fluidity with ease to manipulate data and as a result, a new database model was designed, reflecting these properties. The acronym BASE is slightly more confusing than ACID but however, the words behind it suggest ways in which the BASE model is different and acronym BASE stands for:-\n\n* **B**asically **A**vailable\n* **S**oft state\n* **E**ventual consistency\n\nVisit the following resources to learn more:",
"description": "The rise in popularity of NoSQL databases provided a flexible and fluidity with ease to manipulate data and as a result, a new database model was designed, reflecting these properties. The acronym BASE is slightly more confusing than ACID but however, the words behind it suggest ways in which the BASE model is different and acronym BASE stands for:-\n\n* **B**asically **A**vailable\n* **S**oft state\n* **E**ventual consistency",
"links": []
},
"uqfeiQ9K--QkGNwks4kjk": {
@@ -3793,7 +3782,7 @@
"description": "Public-key cryptography, or asymmetric cryptography, is the field of cryptographic systems that use pairs of related keys. Each key pair consists of a public key and a corresponding private key. Key pairs are generated with cryptographic algorithms based on mathematical problems termed one-way functions.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Public-key cryptography - Wikipedia",
"title": "Public-key Cryptography",
"url": "https://en.wikipedia.org/wiki/Public-key_cryptography",
"type": "article"
},
@@ -3866,7 +3855,12 @@
"type": "opensource"
},
{
"title": "Wikipedia - OWASP",
"title": "OWASP",
"url": "https://owasp.org/",
"type": "article"
},
{
"title": "OWASP - Wiki",
"url": "https://en.wikipedia.org/wiki/OWASP",
"type": "article"
},
@@ -3915,7 +3909,7 @@
},
"1eglba39q426Nh0E0qcdj": {
"title": "How CPU Executes Programs",
"description": "Visit the following resources to learn more:",
"description": "The CPU executes programs by repeatedly fetching instructions from memory, decoding them to understand the operation, and then executing those operations. This cycle, called the fetch-decode-execute cycle, continues for each instruction in the program, with the CPU using registers for temporary storage and a program counter to keep track of the next instruction. Modern CPUs use techniques like pipelining and caches to speed up this process, enabling them to execute complex programs efficiently.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Explore top posts about Computing",
@@ -3923,7 +3917,7 @@
"type": "article"
},
{
"title": "How CPU executes a program",
"title": "How CPU Executes a Program",
"url": "https://www.youtube.com/watch?v=XM4lGflQFvA",
"type": "video"
}
@@ -3931,7 +3925,7 @@
},
"GDLKJkKgB-i7n0YcV2NDa": {
"title": "How Computers Calculate",
"description": "Visit the following resources to learn more:",
"description": "Computers calculate using the binary system, where all data is represented as 0s and 1s. These binary states correspond to the ON/OFF positions of transistors, which are the building blocks of logic gates (AND, OR, NOT). Numbers, characters, and instructions are broken into binary sequences (bits), and grouped into bytes (8 bits). Arithmetic operations like addition are performed through logic gates, which combine binary values. The CPU executes these calculations by following a fetch-decode-execute cycle. Complex calculations, such as handling decimals, use floating-point representation. Programs written in high-level languages are compiled into machine code for the CPU to execute.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "How computers calculate - ALU",
@@ -3953,8 +3947,13 @@
},
"AxiGqbteK7ZSXEUt_zckH": {
"title": "Instructions and Programs",
"description": "Visit the following resources to learn more:",
"description": "Instructions are the most basic commands a CPU can understand, directing it to perform specific actions like adding numbers or moving data. A program, on the other hand, is a collection of these instructions, organized in a sequence to accomplish a particular task. Think of instructions as individual words and a program as a complete sentence or story; the CPU executes these instructions one by one, following the program's logic, to achieve the desired outcome.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Instruction and Programs",
"url": "https://nerdfighteria.info/v/zltgXvg6r3k/",
"type": "article"
},
{
"title": "Instructions and Programs",
"url": "https://youtu.be/zltgXvg6r3k",
@@ -3964,8 +3963,13 @@
},
"DjTQjMbika4_yTzrBpcmB": {
"title": "CPU Cache",
"description": "Visit the following resources to learn more:",
"description": "A CPU cache is a hardware cache used by the central processing unit of a computer to reduce the average cost to access data from the main memory. A cache is a smaller, faster memory, located closer to a processor core, which stores copies of the data from frequently used main memory locations.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is CPU Cache",
"url": "https://www.howtogeek.com/854138/what-is-cpu-cache/",
"type": "article"
},
{
"title": "Explore top posts about Computing",
"url": "https://app.daily.dev/tags/computing?ref=roadmapsh",
@@ -4059,10 +4063,10 @@
},
"xUo5Ox_HTgGyeQMDIkVyK": {
"title": "Concurrency in Multiple Cores",
"description": "Visit the following resources to learn more:",
"description": "Concurrency or Parallelism is simultaneous execution of processes on a multiple cores per CPU or multiple CPUs (on a single motherboard). Concurrency is when Parallelism is achieved on a single core/CPU by using scheduling algorithms that divides the CPU's time (time-slice).\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is the difference between multicore and concurrent programming?",
"title": "Difference between Multi-core and concurrent Programming?",
"url": "https://stackoverflow.com/questions/5372861/what-is-the-difference-between-multicore-and-concurrent-programming",
"type": "article"
},
@@ -4070,34 +4074,18 @@
"title": "Concurrency in Multicore systems",
"url": "https://cs.stackexchange.com/questions/140793/concurrency-in-multiple-core",
"type": "article"
},
{
"title": "Mastering Concurrency",
"url": "https://www.harrisonclarke.com/blog/mastering-concurrency-a-guide-for-software-engineers",
"type": "article"
}
]
},
"Ge2nagN86ofa2y-yYR1lv": {
"title": "Scheduling Algorithms",
"description": "CPU Scheduling is the process of selecting a process from the ready queue and allocating the CPU to it. The selection of a process is based on a particular scheduling algorithm. The scheduling algorithm is chosen depending on the type of system and the requirements of the processes.\n\nHere is the list of some of the most commonly used scheduling algorithms:\n\n* **First Come First Serve (FCFS):** The process that arrives first is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Job First (SJF):** The process with the smallest execution time is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Remaining Time First (SRTF):** The process with the smallest remaining execution time is allocated the CPU first. It is a preemptive algorithm.\n* **Round Robin (RR):** The process is allocated the CPU for a fixed time slice. The time slice is usually 10 milliseconds. It is a preemptive algorithm.\n* **Priority Scheduling:** The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Feedback Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. If a process is preempted, it is moved to the next queue. It is a preemptive algorithm.\n* **Highest Response Ratio Next(HRRN):** CPU is allotted to the next process which has the highest response ratio and not to the process having less burst time. It is a Non-Preemptive algorithm.\n* **Lottery Scheduling:** The process is allocated the CPU based on a lottery system. It is a preemptive algorithm.\n\nVisit the following resources to learn more :",
"links": [
{
"title": "CPU Scheduling in Operating Systems - geeksforgeeks",
"url": "https://www.geeksforgeeks.org/cpu-scheduling-in-operating-systems/",
"type": "article"
},
{
"title": "Lottery Scheduling for Operating Systems - geeksforgeeks",
"url": "https://www.geeksforgeeks.org/lottery-process-scheduling-in-operating-system/",
"type": "article"
},
{
"title": "Program for Round Robin Scheduling for the same Arrival time - geeksforgeeks",
"url": "https://www.geeksforgeeks.org/program-for-round-robin-scheduling-for-the-same-arrival-time/",
"type": "article"
},
{
"title": "Introduction to CPU Scheduling",
"url": "https://youtu.be/EWkQl0n0w5M?si=Lb-PxN_t-rDfn4JL",
"type": "video"
}
]
"description": "CPU Scheduling is the process of selecting a process from the ready queue and allocating the CPU to it. The selection of a process is based on a particular scheduling algorithm. The scheduling algorithm is chosen depending on the type of system and the requirements of the processes.\n\nHere is the list of some of the most commonly used scheduling algorithms:\n\n* **First Come First Serve (FCFS):** The process that arrives first is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Job First (SJF):** The process with the smallest execution time is allocated the CPU first. It is a non-preemptive algorithm.\n* **Shortest Remaining Time First (SRTF):** The process with the smallest remaining execution time is allocated the CPU first. It is a preemptive algorithm.\n* **Round Robin (RR):** The process is allocated the CPU for a fixed time slice. The time slice is usually 10 milliseconds. It is a preemptive algorithm.\n* **Priority Scheduling:** The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. It is a preemptive algorithm.\n* **Multi-level Feedback Queue Scheduling:** The processes are divided into different queues based on their priority. The process with the highest priority is allocated the CPU first. If a process is preempted, it is moved to the next queue. It is a preemptive algorithm.\n* **Highest Response Ratio Next(HRRN):** CPU is allotted to the next process which has the highest response ratio and not to the process having less burst time. It is a Non-Preemptive algorithm.\n* **Lottery Scheduling:** The process is allocated the CPU based on a lottery system. It is a preemptive algorithm.",
"links": []
},
"cpQvB0qMDL3-NWret7oeA": {
"title": "CPU Interrupts",
@@ -4109,7 +4097,7 @@
"type": "article"
},
{
"title": "Video on Interrupts",
"title": "Interrupts",
"url": "https://youtu.be/iKlAWIKEyuw",
"type": "video"
}
@@ -4211,26 +4199,6 @@
"title": "NP Completeness IV",
"url": "https://www.youtube.com/watch?v=NKLDp3Rch3M&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=18",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 23 - NP-Completeness",
"url": "https://www.youtube.com/watch?v=ItHp5laE1VE&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=23",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 24 - Satisfiability",
"url": "https://www.youtube.com/watch?v=inaFJeCzGxU&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=24",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 25 - More NP-Completeness",
"url": "https://www.youtube.com/watch?v=B-bhKxjZLlc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=25",
"type": "video"
},
{
"title": "CSE373 2020 - Lecture 26 - NP-Completeness Challenge",
"url": "https://www.youtube.com/watch?v=_EzetTkG_Cc&list=PLOtl7M3yp-DX6ic0HGT0PUX_wiNmkWkXx&index=26",
"type": "video"
}
]
},

View File

@@ -521,11 +521,6 @@
"title": "Operating Systems",
"description": "**Operating systems (OS)** are software that manage computer hardware and provide a platform for applications to run. They handle essential functions such as managing memory, processing tasks, controlling input and output devices, and facilitating file management. Key examples include **Windows**, **macOS**, **Linux**, and **Unix**. Each operating system offers different features and interfaces, tailored to specific user needs or system requirements, from desktop computing to server management and embedded systems.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is an operating system?",
"url": "https://www.geeksforgeeks.org/what-is-an-operating-system/",
"type": "article"
},
{
"title": "What is an operating system as fast as possible",
"url": "https://www.youtube.com/watch?v=pVzRTmdd9j0",
@@ -708,11 +703,6 @@
"title": "What are Network Protocols?",
"url": "https://www.solarwinds.com/resources/it-glossary/network-protocols",
"type": "article"
},
{
"title": "Types of Network Topology",
"url": "https://www.geeksforgeeks.org/types-of-network-topology/",
"type": "article"
}
]
},
@@ -853,11 +843,6 @@
"title": "loopback",
"description": "**Loopback** refers to a special network interface used to send traffic back to the same device for testing and diagnostic purposes. The loopback address for IPv4 is `127.0.0.1`, while for IPv6 it is `::1`. When a device sends a request to the loopback address, the network data does not leave the local machine; instead, it is processed internally, allowing developers to test applications or network services without requiring external network access. Loopback is commonly used to simulate network traffic, check local services, or debug issues locally.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a loopback address?",
"url": "https://www.geeksforgeeks.org/what-is-a-loopback-address/",
"type": "article"
},
{
"title": "Understanding the loopback address and loopback interfaces",
"url": "https://study-ccna.com/loopback-interface-loopback-address/",
@@ -1256,11 +1241,6 @@
"title": "Star",
"description": "A star network topology is a configuration where all devices (nodes) are connected directly to a central hub or switch. In this arrangement, each node has a dedicated point-to-point link to the central device, forming a star-like structure. This topology offers advantages such as easy installation and reconfiguration, centralized management, and fault isolation. If one connection fails, it doesn't affect others. However, the central hub is a single point of failure for the entire network. Star topologies are commonly used in local area networks (LANs) due to their reliability, scalability, and ease of maintenance, making them a popular choice in both small office and large enterprise environments.\n\nLearn more from the following resources:",
"links": [
{
"title": "Advantages and Disadvantages of Star Topology",
"url": "https://www.geeksforgeeks.org/advantages-and-disadvantages-of-star-topology/",
"type": "article"
},
{
"title": "Star Topology",
"url": "https://www.youtube.com/watch?v=EQ3rW22-Py0",
@@ -1698,11 +1678,6 @@
"title": "Protocol Analyzers",
"description": "**Protocol analyzers**, also known as network analyzers or packet sniffers, are tools used to capture, inspect, and analyze network traffic. They help diagnose network issues, troubleshoot performance problems, and ensure security by providing detailed insights into the data packets transmitted across a network. Protocol analyzers decode and display various network protocols, such as TCP/IP, HTTP, and DNS, allowing users to understand communication patterns, detect anomalies, and identify potential vulnerabilities. Popular examples include Wireshark and tcpdump.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a protocol analyzer?",
"url": "https://www.geeksforgeeks.org/what-is-protocol-analyzer/",
"type": "article"
},
{
"title": "Protocol Analyzers",
"url": "https://www.youtube.com/watch?v=hTMhlB-o0Ow",
@@ -1733,14 +1708,8 @@
},
"xFuWk7M-Vctk_xb7bHbWs": {
"title": "route",
"description": "The `route` command is a network utility used to view and manipulate the IP routing table on Unix-like and Windows systems. It allows users to display the current routes that data packets take, as well as add, modify, or delete routes for network traffic. This command is often used in network troubleshooting and configuration to control how data flows between different networks and subnets. By specifying routes manually, administrators can define specific paths for network traffic, bypassing default routes and optimizing performance or security.\n\nLearn more from the following resources:",
"links": [
{
"title": "How to check the routing table in Linux",
"url": "https://www.geeksforgeeks.org/route-command-in-linux-with-examples/",
"type": "article"
}
]
"description": "The `route` command is a network utility used to view and manipulate the IP routing table on Unix-like and Windows systems. It allows users to display the current routes that data packets take, as well as add, modify, or delete routes for network traffic. This command is often used in network troubleshooting and configuration to control how data flows between different networks and subnets. By specifying routes manually, administrators can define specific paths for network traffic, bypassing default routes and optimizing performance or security.",
"links": []
},
"y8GaUNpaCT1Ai88wPOk6d": {
"title": "tcpdump",
@@ -2308,11 +2277,6 @@
"title": "Understand Handshakes",
"description": "In networking and cybersecurity, a handshake is a process of establishing a secure connection between two parties before data exchange begins. It typically involves a series of predefined messages exchanged to verify identities, agree on communication parameters, and sometimes establish encryption keys. The most common example is the TCP three-way handshake used to initiate a connection. In cryptographic protocols like TLS/SSL, handshakes are more complex, involving certificate verification and key exchange. Handshakes are crucial for ensuring secure, authenticated communications, preventing unauthorized access, and setting up the parameters for efficient data transfer in various network protocols and security systems.\n\nLearn more from the following resources:",
"links": [
{
"title": "TCP 3-Way Handshake Process",
"url": "https://www.geeksforgeeks.org/tcp-3-way-handshake-process/",
"type": "article"
},
{
"title": "TLS Handshake Explained",
"url": "https://www.youtube.com/watch?v=86cQJ0MMses",
@@ -3110,14 +3074,8 @@
},
"W7bcydXdwlubXF2PHKOuq": {
"title": "Port Blocking",
"description": "Port blocking is an essential practice in hardening the security of your network and devices. It involves restricting, filtering, or entirely denying access to specific network ports to minimize exposure to potential cyber threats. By limiting access to certain ports, you can effectively safeguard your systems against unauthorized access and reduce the likelihood of security breaches.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is port blocking with LAN?",
"url": "https://www.geeksforgeeks.org/what-is-port-blocking-within-lan/",
"type": "article"
}
]
"description": "Port blocking is an essential practice in hardening the security of your network and devices. It involves restricting, filtering, or entirely denying access to specific network ports to minimize exposure to potential cyber threats. By limiting access to certain ports, you can effectively safeguard your systems against unauthorized access and reduce the likelihood of security breaches.",
"links": []
},
"FxuMJmDoDkIsPFp2iocFg": {
"title": "Group Policy",

View File

@@ -6,18 +6,19 @@
},
"yCnn-NfSxIybUQ2iTuUGq": {
"title": "What is Data Analytics",
"description": "Data Analytics is a core component of a Data Analyst's role. The field involves extracting meaningful insights from raw data to drive decision-making processes. It includes a wide range of techniques and disciplines ranging from the simple data compilation to advanced algorithms and statistical analysis. As a data analyst, you are expected to understand and interpret complex digital data, such as the usage statistics of a website, the sales figures of a company, or client engagement over social media, etc. This knowledge enables data analysts to support businesses in identifying trends, making informed decisions, predicting potential outcomes - hence playing a crucial role in shaping business strategies.",
"links": []
"description": "Data Analytics is a core component of a Data Analyst's role. The field involves extracting meaningful insights from raw data to drive decision-making processes. It includes a wide range of techniques and disciplines ranging from the simple data compilation to advanced algorithms and statistical analysis. As a data analyst, you are expected to understand and interpret complex digital data, such as the usage statistics of a website, the sales figures of a company, or client engagement over social media, etc. This knowledge enables data analysts to support businesses in identifying trends, making informed decisions, predicting potential outcomes - hence playing a crucial role in shaping business strategies.\n\nLearn more from the following resources:",
"links": [
{
"title": "Introduction to Data Analytics",
"url": "https://www.coursera.org/learn/introduction-to-data-analytics",
"type": "course"
}
]
},
"Lsapbmg-eMIYJAHpV97nO": {
"title": "Types of Data Analytics",
"description": "Data Analytics has proven to be a critical part of decision-making in modern business ventures. It is responsible for discovering, interpreting, and transforming data into valuable information. Different types of data analytics look at past, present, or predictive views of business operations.\n\nData Analysts, as ambassadors of this domain, employ these types, to answer various questions:\n\n* Descriptive Analytics _(what happened in the past?)_\n* Diagnostic Analytics _(why did it happened in the past?)_\n* Predictive Analytics _(what will happen in the future?)_\n* Prescriptive Analytics _(how can we make it happen?)_\n\nUnderstanding these types gives data analysts the power to transform raw datasets into strategic insights.\n\nVisit the following resources to learn more:",
"description": "Data Analytics has proven to be a critical part of decision-making in modern business ventures. It is responsible for discovering, interpreting, and transforming data into valuable information. Different types of data analytics look at past, present, or predictive views of business operations.\n\nData Analysts, as ambassadors of this domain, employ these types, to answer various questions:\n\n* Descriptive Analytics _(what happened in the past?)_\n* Diagnostic Analytics _(why did it happened in the past?)_\n* Predictive Analytics _(what will happen in the future?)_\n* Prescriptive Analytics _(how can we make it happen?)_\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Data Analytics and its type",
"url": "https://www.geeksforgeeks.org/data-analytics-and-its-type/",
"type": "article"
},
{
"title": "The 4 Types of Data Analysis: Ultimate Guide",
"url": "https://careerfoundry.com/en/blog/data-analytics/different-types-of-data-analysis/",
@@ -72,12 +73,12 @@
"description": "Predictive analysis is a crucial type of data analytics that any competent data analyst should comprehend. It refers to the practice of extracting information from existing data sets in order to determine patterns and forecast future outcomes and trends. Data analysts apply statistical algorithms, machine learning techniques, and artificial intelligence to the data to anticipate future results. Predictive analysis enables organizations to be proactive, forward-thinking, and strategic by providing them valuable insights on future occurrences. It's a powerful tool that gives companies a significant competitive edge by enabling risk management, opportunity identification, and strategic decision-making.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is predictive analytics? - Google",
"title": "What is Predictive Analytics? - Google",
"url": "https://cloud.google.com/learn/what-is-predictive-analytics",
"type": "article"
},
{
"title": "What is predictive analytics?",
"title": "What is Predictive Analytics?",
"url": "https://www.youtube.com/watch?v=cVibCHRSxB0",
"type": "video"
}
@@ -125,7 +126,7 @@
"description": "The Cleanup of Data is a critical component of a Data Analyst's role. It involves the process of inspecting, cleaning, transforming, and modeling data to discover useful information, inform conclusions, and support decision making. This process is crucial for Data Analysts to generate accurate and significant insights from data, ultimately resulting in better and more informed business decisions. A solid understanding of data cleanup procedures and techniques is a fundamental skill for any Data Analyst. Hence, it is necessary to hold a high emphasis on maintaining data quality by managing data integrity, accuracy, and consistency during the data cleanup process.\n\nLearn more from the following resources:",
"links": [
{
"title": "Top 10 ways to clean your data",
"title": "Top 10 Ways to Clean Your Data",
"url": "https://support.microsoft.com/en-gb/office/top-ten-ways-to-clean-your-data-2844b620-677c-47a7-ac3e-c2e157d1db19",
"type": "article"
},
@@ -157,7 +158,7 @@
"description": "The visualization of data is an essential skill in the toolkit of every data analyst. This practice is about transforming complex raw data into a graphical format that allows for an easier understanding of large data sets, trends, outliers, and important patterns. Whether pie charts, line graphs, bar graphs, or heat maps, data visualization techniques not only streamline data analysis, but also facilitate a more effective communication of the findings to others. This key concept underscores the importance of presenting data in a digestible and visually appealing manner to drive data-informed decision making in an organization.\n\nLearn more from the following resources:",
"links": [
{
"title": "Data visualization beginner's guide",
"title": "Data Visualization Beginner's Guide",
"url": "https://www.tableau.com/en-gb/learn/articles/data-visualization",
"type": "article"
},
@@ -223,7 +224,7 @@
},
"yBlJrNo9eO470dLp6OaQZ": {
"title": "DATEDIF",
"description": "The `DATEDIF` function is an incredibly valuable tool for a Data Analyst in Excel or Google Sheets, by providing the ability to calculate the difference between two dates. This function takes in three parameters: start date, end date and the type of difference required (measured in years, months, days, etc.). In Data Analysis, particularly when dealing with time-series data or when you need to uncover trends over specific periods, the `DATEDIF` function is a necessary asset. Recognizing its functionality will enable a data analyst to manipulate or shape data progressively and efficiently.\n\n* `DATEDIF` is technically still supported, but wont show as an option. For additional information, see Excel \"Help\" page.\n\nLearn more from the following resources:",
"description": "The `DATEDIF` function is an incredibly valuable tool for a Data Analyst in Excel or Google Sheets, by providing the ability to calculate the difference between two dates. This function takes in three parameters: start date, end date and the type of difference required (measured in years, months, days, etc.). In Data Analysis, particularly when dealing with time-series data or when you need to uncover trends over specific periods, the `DATEDIF` function is a necessary asset. Recognizing its functionality will enable a data analyst to manipulate or shape data progressively and efficiently.\n\n`DATEDIF` is technically still supported, but wont show as an option. For additional information, see Excel \"Help\" page.\n\nLearn more from the following resources:",
"links": [
{
"title": "DATEDIF function",
@@ -259,7 +260,7 @@
"links": [
{
"title": "Replace Function",
"url": "https://support.microsoft.com/en-us/office/replace-function-6acf209b-01b7-4078-b4b8-e0a4ef67d181",
"url": "https://support.microsoft.com/en-us/office/replace-function-8d799074-2425-4a8a-84bc-82472868878a",
"type": "article"
},
{
@@ -407,17 +408,17 @@
"description": "Data Analysts recurrently find the need to summarize, investigate, and analyze their data to make meaningful and insightful decisions. One of the most powerful tools to accomplish this in Microsoft Excel is the Pivot Table. Pivot Tables allow analysts to organize and summarize large quantities of data in a concise, tabular format. The strength of pivot tables comes from their ability to manipulate data dynamically, leading to quicker analysis and richer insights. Understanding and employing Pivot Tables efficiently is a fundamental skill for any data analyst, as it directly impacts their ability to derive significant information from raw datasets.\n\nLearn more from the following resources:",
"links": [
{
"title": "Create a pivot table",
"title": "Create a Pivot Table",
"url": "https://support.microsoft.com/en-gb/office/create-a-pivottable-to-analyze-worksheet-data-a9a84538-bfe9-40a9-a8e9-f99134456576",
"type": "article"
},
{
"title": "Pivot tables in excel",
"title": "Pivot Tables in Excel",
"url": "https://www.excel-easy.com/data-analysis/pivot-tables.html",
"type": "article"
},
{
"title": "How to create a pivot table in excel",
"title": "How to Create a Pivot Table in Excel",
"url": "https://www.youtube.com/watch?v=PdJzy956wo4",
"type": "video"
}
@@ -452,15 +453,31 @@
},
"M1QtGTLyygIjePoCfvjve": {
"title": "Data Manipulation Libraries",
"description": "Data manipulation libraries are essential tools in data science and analytics, enabling efficient handling, transformation, and analysis of large datasets. Python, a popular language for data science, offers several powerful libraries for this purpose. Pandas is a highly versatile library that provides data structures like DataFrames, which allow for easy manipulation and analysis of tabular data. NumPy, another fundamental library, offers support for large, multi-dimensional arrays and matrices, along with a collection of mathematical functions to operate on these arrays. Together, Pandas and NumPy form the backbone of data manipulation in Python, facilitating tasks such as data cleaning, merging, reshaping, and statistical analysis, thus streamlining the data preparation process for machine learning and other data-driven applications.",
"links": []
"description": "Data manipulation libraries are essential tools in data science and analytics, enabling efficient handling, transformation, and analysis of large datasets. Python, a popular language for data science, offers several powerful libraries for this purpose. Pandas is a highly versatile library that provides data structures like DataFrames, which allow for easy manipulation and analysis of tabular data. NumPy, another fundamental library, offers support for large, multi-dimensional arrays and matrices, along with a collection of mathematical functions to operate on these arrays. Together, Pandas and NumPy form the backbone of data manipulation in Python, facilitating tasks such as data cleaning, merging, reshaping, and statistical analysis, thus streamlining the data preparation process for machine learning and other data-driven applications.\n\nLearn more from the following resources:",
"links": [
{
"title": "Pandas",
"url": "https://pandas.pydata.org/",
"type": "article"
},
{
"title": "NumPy",
"url": "https://numpy.org/",
"type": "article"
},
{
"title": "Top Python Libraries for Data Science",
"url": "https://www.simplilearn.com/top-python-libraries-for-data-science-article",
"type": "article"
}
]
},
"8OXmF2Gn6TYJotBRvDjqA": {
"title": "Pandas",
"description": "Pandas is a widely acknowledged and highly useful data manipulation library in the world of data analysis. Known for its robust features like data cleaning, wrangling and analysis, pandas has become one of the go-to tools for data analysts. Built on NumPy, it provides high-performance, easy-to-use data structures and data analysis tools. In essence, its flexibility and versatility make it a critical part of the data analyst's toolkit, as it holds the capability to cater to virtually every data manipulation task.\n\nLearn more from the following resources:",
"links": [
{
"title": "Pandas Website",
"title": "Pandas",
"url": "https://pandas.pydata.org/",
"type": "article"
},
@@ -473,7 +490,7 @@
},
"l1SnPc4EMqGdaIAhIQfrT": {
"title": "Data Visualisation Libraries",
"description": "Data visualization libraries are crucial in data science for transforming complex datasets into clear and interpretable visual representations, facilitating better understanding and communication of data insights. In Python, several libraries are widely used for this purpose. Matplotlib is a foundational library that offers comprehensive tools for creating static, animated, and interactive plots. Seaborn, built on top of Matplotlib, provides a high-level interface for drawing attractive and informative statistical graphics with minimal code. Plotly is another powerful library that allows for the creation of interactive and dynamic visualizations, which can be easily embedded in web applications. Additionally, libraries like Bokeh and Altair offer capabilities for creating interactive plots and dashboards, enhancing exploratory data analysis and the presentation of data findings. Together, these libraries enable data scientists to effectively visualize trends, patterns, and outliers in their data, making the analysis more accessible and actionable.",
"description": "Data visualization libraries are crucial in data science for transforming complex datasets into clear and interpretable visual representations, facilitating better understanding and communication of data insights. In Python, several libraries are widely used for this purpose. Matplotlib is a foundational library that offers comprehensive tools for creating static, animated, and interactive plots. Seaborn, built on top of Matplotlib, provides a high-level interface for drawing attractive and informative statistical graphics with minimal code. Plotly is another powerful library that allows for the creation of interactive and dynamic visualizations, which can be easily embedded in web applications. Additionally, libraries like Bokeh and Altair offer capabilities for creating interactive plots and dashboards, enhancing exploratory data analysis and the presentation of data findings. Together, these libraries enable data scientists to effectively visualize trends, patterns, and outliers in their data, making the analysis more accessible and actionable.\n\nLearn more from the following resources:",
"links": []
},
"uGkXxdMXUMY-3fQFS1jK8": {
@@ -481,7 +498,7 @@
"description": "Matplotlib is a paramount data visualization library used extensively by data analysts for generating a wide array of plots and graphs. Through Matplotlib, data analysts can convey results clearly and effectively, driving insights from complex data sets. It offers a hierarchical environment which is very natural for a data scientist to work with. Providing an object-oriented API, it allows for extensive customization and integration into larger applications. From histograms, bar charts, scatter plots to 3D graphs, the versatility of Matplotlib assists data analysts in the better comprehension and compelling representation of data.\n\nLearn more from the following resources:",
"links": [
{
"title": "Matplotlib Website",
"title": "Matplotlib",
"url": "https://matplotlib.org/",
"type": "article"
},
@@ -497,7 +514,7 @@
"description": "Dplyr is a powerful and popular toolkit for data manipulation in R. As a data analyst, this library provides integral functions to manipulate, clean, and process data efficiently. It has been designed to be easy and intuitive, ensuring a robust and consistent syntax. Dplyr ensures data reliability and fast processing, essential for analysts dealing with large datasets. With a strong focus on efficiency, dplyr functions like select, filter, arrange, mutate, summarise, and group\\_by optimise data analysis operations, making data manipulation a smoother and hassle-free procedure for data analysts.\n\nLearn more from the following resources:",
"links": [
{
"title": "dplyr website",
"title": "dplyr",
"url": "https://dplyr.tidyverse.org/",
"type": "article"
},
@@ -513,7 +530,7 @@
"description": "When it comes to data visualization in R programming, ggplot2 stands tall as one of the primary tools for data analysts. This data visualization library, which forms part of the tidyverse suite of packages, facilitates the creation of complex and sophisticated visual narratives. With its grammar of graphics philosophy, ggplot2 enables analysts to build graphs and charts layer by layer, thereby offering detailed control over graphical features and design. Its versatility in creating tailored and aesthetically pleasing graphics is a vital asset for any data analyst tackling exploratory data analysis, reporting, or dashboard building.\n\nLearn more from the following resources:",
"links": [
{
"title": "ggplot2 website",
"title": "ggplot2",
"url": "https://ggplot2.tidyverse.org/",
"type": "article"
},
@@ -526,21 +543,27 @@
},
"_sjXCLHHTbZromJYn6fnu": {
"title": "Data Collection",
"description": "In the context of the Data Analyst role, data collection is a foundational process that entails gathering relevant data from various sources. This data can be quantitative or qualitative and may be sourced from databases, online platforms, customer feedback, among others. The gathered information is then cleaned, processed, and interpreted to extract meaningful insights. A data analyst performs this whole process carefully, as the quality of data is paramount to ensuring accurate analysis, which in turn informs business decisions and strategies. This highlights the importance of an excellent understanding, proper tools, and precise techniques when it comes to data collection in data analysis.",
"links": []
"description": "Data collection is a foundational process that entails gathering relevant data from various sources. This data can be quantitative or qualitative and may be sourced from databases, online platforms, customer feedback, among others. The gathered information is then cleaned, processed, and interpreted to extract meaningful insights. A data analyst performs this whole process carefully, as the quality of data is paramount to ensuring accurate analysis, which in turn informs business decisions and strategies. This highlights the importance of an excellent understanding, proper tools, and precise techniques when it comes to data collection in data analysis.\n\nLearn more from the following resources:",
"links": [
{
"title": "Data Collection",
"url": "https://en.wikipedia.org/wiki/Data_collection",
"type": "article"
}
]
},
"tYPeLCxbqvMFlTkCGjdHg": {
"title": "Databases",
"description": "Behind every strong data analyst, there's not just a rich assortment of data, but a set of robust databases that enable effective data collection. Databases are a fundamental aspect of data collection in a world where the capability to manage, organize, and evaluate large volumes of data is critical. As a data analyst, the understanding and use of databases is instrumental in capturing the necessary data for conducting qualitative and quantitative analysis, forecasting trends and making data-driven decisions. Thorough knowledge of databases, therefore, can be considered a key component of a data analyst's arsenal. These databases can vary from relational databases like SQL to NoSQL databases like MongoDB, each serving a unique role in the data collection process.\n\nLearn more from the following resources:",
"links": [
{
"title": "PostgreSQL Roadmap",
"url": "https://roadmap.sh/postgresql-dba",
"title": "Visit Dedicated SQL Roadmap",
"url": "https://roadmap.sh/sql",
"type": "article"
},
{
"title": "MongoDB Roadmap",
"url": "https://roadmap.sh/mongodb",
"title": "Visit Dedicated PostgreSQL Roadmap",
"url": "https://roadmap.sh/postgresql-dba",
"type": "article"
}
]
@@ -571,7 +594,7 @@
"type": "article"
},
{
"title": "A beginners guide to APIs",
"title": "A Beginner's Guide to APIs",
"url": "https://www.postman.com/what-is-an-api/",
"type": "article"
}
@@ -582,12 +605,12 @@
"description": "Web scraping plays a significant role in collecting unique datasets for data analysis. In the realm of a data analyst's tasks, web scraping refers to the method of extracting information from websites and converting it into a structured usable format like a CSV, Excel spreadsheet, or even into databases. This technique allows data analysts to gather large sets of data from the internet, which otherwise could be time-consuming if done manually. The capability of web scraping and parsing data effectively can give data analysts a competitive edge in their data analysis process, from unlocking in-depth, insightful information to making data-driven decisions.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is web scraping what is it used for?",
"title": "What is Web Scraping & What is it used for?",
"url": "https://www.parsehub.com/blog/what-is-web-scraping/",
"type": "article"
},
{
"title": "What is web scraping?",
"title": "What is Web Scraping?",
"url": "https://www.youtube.com/watch?v=dlj_QL-ENJM",
"type": "video"
}
@@ -595,8 +618,14 @@
},
"E6cpb6kvluJM8OGuDcFBT": {
"title": "Data Cleanup",
"description": "Data cleaning, which is often referred as data cleansing or data scrubbing, is one of the most important and initial steps in the data analysis process. As a data analyst, the bulk of your work often revolves around understanding, cleaning, and standardizing raw data before analysis. Data cleaning involves identifying, correcting or removing any errors or inconsistencies in datasets in order to improve their quality. The process is crucial because it directly determines the accuracy of the insights you generate - garbage in, garbage out. Even the most sophisticated models and visualizations would not be of much use if they're based on dirty data. Therefore, mastering data cleaning techniques is essential for any data analyst.",
"links": []
"description": "Data cleaning, which is often referred as data cleansing or data scrubbing, is one of the most important and initial steps in the data analysis process. As a data analyst, the bulk of your work often revolves around understanding, cleaning, and standardizing raw data before analysis. Data cleaning involves identifying, correcting or removing any errors or inconsistencies in datasets in order to improve their quality. The process is crucial because it directly determines the accuracy of the insights you generate - garbage in, garbage out. Even the most sophisticated models and visualizations would not be of much use if they're based on dirty data. Therefore, mastering data cleaning techniques is essential for any data analyst.\n\nLearn more from the following resources:",
"links": [
{
"title": "Data Cleaning",
"url": "https://www.tableau.com/learn/articles/what-is-data-cleaning#:~:text=tools%20and%20software-,What%20is%20data%20cleaning%3F,to%20be%20duplicated%20or%20mislabeled.",
"type": "article"
}
]
},
"X9WmfHOks82BIAzs6abqO": {
"title": "Handling Missing Data",
@@ -636,14 +665,14 @@
"links": [
{
"title": "Outliers",
"url": "%5Bhttps://www.mathsisfun.com/data/outliers.html",
"url": "https://www.mathsisfun.com/data/outliers.html",
"type": "article"
}
]
},
"t_BRtEharsrOZxoyX0OzV": {
"title": "Data Transformation",
"description": "Data Transformation, also known as Data Wrangling, is an essential part of a Data Analyst's role. This process involves the conversion of data from a raw format into another format to make it more appropriate and valuable for a variety of downstream purposes such as analytics. Data Analysts transform data to make the data more suitable for analysis, ensure accuracy, and to improve data quality. The right transformation techniques can give the data a structure, multiply its value, and enhance the accuracy of the analytics performed by serving meaningful results.",
"description": "Data Transformation, also known as Data Wrangling, is an essential part of a Data Analyst's role. This process involves the conversion of data from a raw format into another format to make it more appropriate and valuable for a variety of downstream purposes such as analytics. Data Analysts transform data to make the data more suitable for analysis, ensure accuracy, and to improve data quality. The right transformation techniques can give the data a structure, multiply its value, and enhance the accuracy of the analytics performed by serving meaningful results.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is data transformation?",
@@ -662,7 +691,7 @@
"description": "In the realms of data analysis, data cleaning is a crucial preliminary process, this is where `pandas` - a popular python library - shines. Primarily used for data manipulation and analysis, pandas adopts a flexible and powerful data structure (DataFrames and Series) that greatly simplifies the process of cleaning raw, messy datasets. Data analysts often work with large volumes of data, some of which may contain missing or inconsistent data that can negatively impact the results of their analysis. By utilizing pandas, data analysts can quickly identify, manage and fill these missing values, drop unnecessary columns, rename column headings, filter specific data, apply functions for more complex data transformations and much more. Thus, making pandas an invaluable tool for effective data cleaning in data analysis.\n\nLearn more from the following resources:",
"links": [
{
"title": "Pandas Website",
"title": "Pandas",
"url": "https://pandas.pydata.org/",
"type": "article"
},
@@ -678,7 +707,7 @@
"description": "Data cleaning plays a crucial role in the data analysis pipeline, where it rectifies and enhances the quality of data to increase the efficiency and authenticity of the analytical process. The `dplyr` package, an integral part of the `tidyverse` suite in R, has become a staple in the toolkit of data analysts dealing with data cleaning. `dplyr` offers a coherent set of verbs that significantly simplifies the process of manipulating data structures, such as dataframes and databases. This involves selecting, sorting, filtering, creating or modifying variables, and aggregating records, among other operations. Incorporating `dplyr` into the data cleaning phase enables data analysts to perform operations more effectively, improve code readability, and handle large and complex data with ease.\n\nLearn more from the following resources:",
"links": [
{
"title": "dplyr website",
"title": "dplyr",
"url": "https://dplyr.tidyverse.org/",
"type": "article"
},
@@ -790,7 +819,7 @@
"description": "When focusing on data analysis, understanding key statistical concepts is crucial. Amongst these, central tendency is a foundational element. Central Tendency refers to the measure that determines the center of a distribution. The average is a commonly used statistical tool by which data analysts discern trends and patterns. As one of the most recognized forms of central tendency, figuring out the \"average\" involves summing all values in a data set and dividing by the number of values. This provides analysts with a 'typical' value, around which the remaining data tends to cluster, facilitating better decision-making based on existing data.\n\nLearn more from the following resources:",
"links": [
{
"title": "How to calculate the average",
"title": "How to Calculate the Average",
"url": "https://support.microsoft.com/en-gb/office/calculate-the-average-of-a-group-of-numbers-e158ef61-421c-4839-8290-34d7b1e68283#:~:text=Average%20This%20is%20the%20arithmetic,by%206%2C%20which%20is%205.",
"type": "article"
},
@@ -806,7 +835,7 @@
"description": "The concept of Range refers to the spread of a dataset, primarily in the realm of statistics and data analysis. This measure is crucial for a data analyst as it provides an understanding of the variability amongst the numbers within a dataset. Specifically in a role such as Data Analyst, understanding the range and dispersion aids in making more precise analyses and predictions. Understanding the dispersion within a range can highlight anomalies, identify standard norms, and form the foundation for statistical conclusions like the standard deviation, variance, and interquartile range. It allows for the comprehension of the reliability and stability of particular datasets, which can help guide strategic decisions in many industries. Therefore, range is a key concept that every data analyst must master.\n\nLearn more from the following resources:",
"links": [
{
"title": "How to find the range of a data set",
"title": "How to Find the Range of a Data Set",
"url": "https://www.scribbr.co.uk/stats/range-statistics/",
"type": "article"
}
@@ -817,12 +846,12 @@
"description": "Data analysts heavily rely on statistical concepts to analyze and interpret data, and one such fundamental concept is variance. Variance, an essential measure of dispersion, quantifies the spread of data, providing insight into the level of variability within the dataset. Understanding variance is crucial for data analysts as the reliability of many statistical models depends on the assumption of constant variance across observations. In other words, it helps analysts determine how much data points diverge from the expected value or mean, which can be pivotal in identifying outliers, understanding data distribution, and driving decision-making processes. However, variance can't be interpreted in the original units of measurement due to its squared nature, which is why it is often used in conjunction with its square root, the standard deviation.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is variance?",
"title": "What is Variance?",
"url": "https://www.investopedia.com/terms/v/variance.asp",
"type": "article"
},
{
"title": "How to calculate variance",
"title": "How to Calculate Variance",
"url": "https://www.scribbr.co.uk/stats/variance-meaning/",
"type": "article"
}
@@ -892,7 +921,7 @@
"description": "Tableau is a powerful data visualization tool utilized extensively by data analysts worldwide. Its primary role is to transform raw, unprocessed data into an understandable format without any technical skills or coding. Data analysts use Tableau to create data visualizations, reports, and dashboards that help businesses make more informed, data-driven decisions. They also use it to perform tasks like trend analysis, pattern identification, and forecasts, all within a user-friendly interface. Moreover, Tableau's data visualization capabilities make it easier for stakeholders to understand complex data and act on insights quickly.\n\nLearn more from the following resources:",
"links": [
{
"title": "Tableau Website",
"title": "Tableau",
"url": "https://www.tableau.com/en-gb",
"type": "article"
},
@@ -908,7 +937,7 @@
"description": "PowerBI, an interactive data visualization and business analytics tool developed by Microsoft, plays a crucial role in the field of a data analyst's work. It helps data analysts to convert raw data into meaningful insights through it's easy-to-use dashboards and reports function. This tool provides a unified view of business data, allowing analysts to track and visualize key performance metrics and make better-informed business decisions. With PowerBI, data analysts also have the ability to manipulate and produce visualizations of large data sets that can be shared across an organization, making complex statistical information more digestible.\n\nLearn more from the following resources:",
"links": [
{
"title": "Power BI Website",
"title": "Power BI",
"url": "https://www.microsoft.com/en-us/power-platform/products/power-bi",
"type": "article"
},
@@ -924,7 +953,7 @@
"description": "For a Data Analyst, understanding data and being able to represent it in a visually insightful form is a crucial part of effective decision-making in any organization. Matplotlib, a plotting library for the Python programming language, is an extremely useful tool for this purpose. It presents a versatile framework for generating line plots, scatter plots, histogram, bar charts and much more in a very straightforward manner. This library also allows for comprehensive customizations, offering a high level of control over the look and feel of the graphics it produces, which ultimately enhances the quality of data interpretation and communication.\n\nLearn more from the following resources:",
"links": [
{
"title": "Matplotlib Website",
"title": "Matplotlib",
"url": "https://matplotlib.org/",
"type": "article"
},
@@ -940,7 +969,7 @@
"description": "Seaborn is a robust, comprehensive Python library focused on the creation of informative and attractive statistical graphics. As a data analyst, seaborn plays an essential role in elaborating complex visual stories with the data. It aids in understanding the data by providing an interface for drawing attractive and informative statistical graphics. Seaborn is built on top of Python's core visualization library Matplotlib, and is integrated with data structures from Pandas. This makes seaborn an integral tool for data visualization in the data analyst's toolkit, making the exploration and understanding of data easier and more intuitive.\n\nLearn more from the following resources:",
"links": [
{
"title": "Seaborn Website",
"title": "Seaborn",
"url": "https://seaborn.pydata.org/",
"type": "article"
},
@@ -972,12 +1001,12 @@
"description": "As a vital tool in the data analyst's arsenal, bar charts are essential for analyzing and interpreting complex data. Bar charts, otherwise known as bar graphs, are frequently used graphical displays for dealing with categorical data groups or discrete variables. With their stark visual contrast and definitive measurements, they provide a simple yet effective means of identifying trends, understanding data distribution, and making data-driven decisions. By analyzing the lengths or heights of different bars, data analysts can effectively compare categories or variables against each other and derive meaningful insights effectively. Simplicity, readability, and easy interpretation are key features that make bar charts a favorite in the world of data analytics.\n\nLearn more from the following resources:",
"links": [
{
"title": "A complete guide to bar charts",
"title": "A Complete Guide to Bar Charts",
"url": "https://www.atlassian.com/data/charts/bar-chart-complete-guide",
"type": "article"
},
{
"title": "What is a bar chart?",
"title": "What is a Bar Chart?",
"url": "https://www.youtube.com/watch?v=WTVdncVCvKo",
"type": "video"
}
@@ -1004,7 +1033,7 @@
"description": "A scatter plot, a crucial aspect of data visualization, is a mathematical diagram using Cartesian coordinates to represent values from two different variables. As a data analyst, understanding and interpreting scatter plots can be instrumental in identifying correlations and trends within a dataset, drawing meaningful insights, and showcasing these findings in a clear, visual manner. In addition, scatter plots are paramount in predictive analytics as they reveal patterns which can be used to predict future occurrences.\n\nLearn more from the following resources:",
"links": [
{
"title": "Mastering scatter plots",
"title": "Mastering Scatter Plots",
"url": "https://www.atlassian.com/data/charts/what-is-a-scatter-plot",
"type": "article"
},
@@ -1052,7 +1081,7 @@
"description": "A stacked chart is an essential tool for a data analyst in the field of data visualization. This type of chart presents quantitative data in a visually appealing manner and allows users to easily compare different categories while still being able to compare the total sizes. These charts are highly effective when trying to measure part-to-whole relationships, displaying accumulated totals over time or when presenting data with multiple variables. Data analysts often use stacked charts to detect patterns, trends and anomalies which can aid in strategic decision making.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a stacked chart?",
"title": "What is a Stacked Chart?",
"url": "https://www.spotfire.com/glossary/what-is-a-stacked-chart",
"type": "article"
},
@@ -1068,12 +1097,12 @@
"description": "Heatmaps are a crucial component of data visualization that Data Analysts regularly employ in their analyses. As one of many possible graphical representations of data, heatmaps show the correlation or scale of variation between two or more variables in a dataset, making them extremely useful for pattern recognition and outlier detection. Individual values within a matrix are represented in a heatmap as colors, with differing intensities indicating the degree or strength of an occurrence. In short, a Data Analyst would use a heatmap to decode complex multivariate data and turn it into an easily understandable visual that aids in decision making.\n\nLearn more from the following resources:",
"links": [
{
"title": "A complete guide to heatmaps",
"title": "A Complete Guide to Heatmaps",
"url": "https://www.hotjar.com/heatmaps/",
"type": "article"
},
{
"title": "What is a heatmap?",
"title": "What is a Heatmap?",
"url": "https://www.atlassian.com/data/charts/heatmap-complete-guide",
"type": "article"
}
@@ -1084,12 +1113,12 @@
"description": "As a data analyst, understanding and efficiently using various forms of data visualization is crucial. Among these, Pie Charts represent a significant tool. Essentially, pie charts are circular statistical graphics divided into slices to illustrate numerical proportions. Each slice of the pie corresponds to a particular category. The pie chart's beauty lies in its simplicity and visual appeal, making it an effective way to convey relative proportions or percentages at a glance. For a data analyst, it's particularly useful when you want to show a simple distribution of categorical data. Like any tool, though, it's important to use pie charts wisely—ideally, when your data set has fewer than seven categories, and the proportions between categories are distinct.\n\nLearn more from the following resources:",
"links": [
{
"title": "A complete guide to pie charts",
"title": "A Complete Guide to Pie Charts",
"url": "https://www.atlassian.com/data/charts/pie-chart-complete-guide",
"type": "article"
},
{
"title": "What is a a pie chart",
"title": "What is a Pie Chart",
"url": "https://www.youtube.com/watch?v=GjJdZaQrItg",
"type": "video"
}
@@ -1097,13 +1126,30 @@
},
"2g19zjEASJw2ve57hxpr0": {
"title": "Data Visualisation",
"description": "Data Visualization is a fundamental fragment of the responsibilities of a data analyst. It involves the presentation of data in a graphical or pictorial format which allows decision-makers to see analytics visually. This practice can help them comprehend difficult concepts or establish new patterns. With interactive visualization, data analysts can take the data analysis process to a whole new level — drill down into charts and graphs for more detail, and interactively changing what data is presented or how its processed. Thereby it forms a crucial link in the chain of converting raw data to actionable insights which is one of the primary roles of a Data Analyst.",
"links": []
"description": "Data Visualization is a fundamental fragment of the responsibilities of a data analyst. It involves the presentation of data in a graphical or pictorial format which allows decision-makers to see analytics visually. This practice can help them comprehend difficult concepts or establish new patterns. With interactive visualization, data analysts can take the data analysis process to a whole new level — drill down into charts and graphs for more detail, and interactively changing what data is presented or how its processed. Thereby it forms a crucial link in the chain of converting raw data to actionable insights which is one of the primary roles of a Data Analyst.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is Data Visualization?",
"url": "https://www.ibm.com/think/topics/data-visualization",
"type": "article"
}
]
},
"TeewVruErSsD4VLXcaDxp": {
"title": "Statistical Analysis",
"description": "Statistical analysis is a core component of a data analyst's toolkit. As professionals dealing with vast amount of structured and unstructured data, data analysts often turn to statistical methods to extract insights and make informed decisions. The role of statistical analysis in data analytics involves gathering, reviewing, and interpreting data for various applications, enabling businesses to understand their performance, trends, and growth potential. Data analysts use a range of statistical techniques from modeling, machine learning, and data mining, to convey vital information that supports strategic company actions.\n\nLearn more from the following resources:",
"links": []
"links": [
{
"title": "Understanding Statistical Analysis",
"url": "https://www.simplilearn.com/what-is-statistical-analysis-article",
"type": "article"
},
{
"title": "Statistical Analysis",
"url": "https://www.youtube.com/watch?v=XjMBZE1DuBY",
"type": "video"
}
]
},
"Xygwu0m5TeYT6S_8FKKXh": {
"title": "Hypothesis Testing",
@@ -1155,7 +1201,7 @@
},
"mCUW07rx74_dUNi7OGVlj": {
"title": "Visualizing Distributions",
"description": "Visualising Distributions, from a data analyst's perspective, plays a key role in understanding the overall distribution and identifying patterns within data. It aids in summarising, structuring, and plotting structured data graphically to provide essential insights. This includes using different chart types like bar graphs, histograms, and scatter plots for interval data, and pie or bar graphs for categorical data. Ultimately, the aim is to provide a straightforward and effective manner to comprehend the data's characteristics and underlying structure. A data analyst uses these visualisation techniques to make initial conclusions, detect anomalies, and decide on further analysis paths.\n\nLearn more from the following resources:",
"description": "Visualising Distributions, from a data analyst's perspective, plays a key role in understanding the overall distribution and identifying patterns within data. It aids in summarizing, structuring, and plotting structured data graphically to provide essential insights. This includes using different chart types like bar graphs, histograms, and scatter plots for interval data, and pie or bar graphs for categorical data. Ultimately, the aim is to provide a straightforward and effective manner to comprehend the data's characteristics and underlying structure. A data analyst uses these visualisation techniques to make initial conclusions, detect anomalies, and decide on further analysis paths.\n\nLearn more from the following resources:",
"links": [
{
"title": "Data Visualizations that Capture Distributions",
@@ -1195,12 +1241,12 @@
"description": "Unsupervised learning, as a fundamental aspect of Machine Learning, holds great implications in the realm of data analytics. It is an approach where a model learns to identify patterns and relationships within a dataset that isn't labelled or classified. It is especially useful for a Data Analyst as it can assist in recognizing unforeseen trends, providing new insights or preparing data for other machine learning tasks. This ability to infer without direct supervision allows a vast potential for latent structure discovery and new knowledge derivation from raw data.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is unsupervised learning?",
"title": "What is Unsupervised Learning?",
"url": "https://cloud.google.com/discover/what-is-unsupervised-learning",
"type": "article"
},
{
"title": "Introduction to unsupervised learning",
"title": "Introduction to Unsupervised Learning",
"url": "https://www.datacamp.com/blog/introduction-to-unsupervised-learning",
"type": "article"
}
@@ -1211,7 +1257,7 @@
"description": "Supervised machine learning forms an integral part of the toolset for a Data Analyst. With a direct focus on building predictive models from labeled datasets, it involves training an algorithm based on these known inputs and outputs, helping Data Analysts establish correlations and make reliable predictions. Fortifying a Data Analyst's role, supervised machine learning enables the accurate interpretation of complex data, enhancing decision-making processes.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is supervised learning?",
"title": "What is Supervised Learning?",
"url": "https://cloud.google.com/discover/what-is-supervised-learning",
"type": "article"
},
@@ -1275,12 +1321,12 @@
"description": "As a data analyst, it's crucial to understand various model evaluation techniques. These techniques involve different methods to measure the performance or accuracy of machine learning models. For instance, using confusion matrix, precision, recall, F1 score, ROC curves or Root Mean Squared Error (RMSE) among others. Knowing how to apply these techniques effectively not only helps in selecting the best model for a specific problem but also guides in tuning the performance of the models for optimal results. Understanding these model evaluation techniques also allows data analysts to interpret evaluation results and determine the effectiveness and applicability of a model.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is model evaluation",
"title": "What is Model Evaluation",
"url": "https://domino.ai/data-science-dictionary/model-evaluation",
"type": "article"
},
{
"title": "Model evaluation metrics",
"title": "Model Evaluation Metrics",
"url": "https://www.markovml.com/blog/model-evaluation-metrics",
"type": "article"
}
@@ -1288,8 +1334,14 @@
},
"_aUQZWUhFRvNu0MZ8CPit": {
"title": "Big Data Technologies",
"description": "In the modern digitized world, Big Data refers to extremely large datasets that are challenging to manage and analyze using traditional data processing applications. These datasets often come from numerous different sources and are not only voluminous but also diverse in nature, including structured and unstructured data. The role of a data analyst in the context of big data is crucial. Data analysts are responsible for inspecting, cleaning, transforming, and modeling big data to discover useful information, conclude and support decision-making. They leverage their analytical skills and various big data tools and technologies to extract insights that can benefit the organization and drive strategic business initiatives.",
"links": []
"description": "In the modern digitized world, Big Data refers to extremely large datasets that are challenging to manage and analyze using traditional data processing applications. These datasets often come from numerous different sources and are not only voluminous but also diverse in nature, including structured and unstructured data. The role of a data analyst in the context of big data is crucial. Data analysts are responsible for inspecting, cleaning, transforming, and modeling big data to discover useful information, conclude and support decision-making. They leverage their analytical skills and various big data tools and technologies to extract insights that can benefit the organization and drive strategic business initiatives.\n\nLearn more from the following resources:",
"links": [
{
"title": "Big Data Analytics",
"url": "https://www.ibm.com/think/topics/big-data-analytics",
"type": "article"
}
]
},
"m1IfG2sEedUxMXrv_B8GW": {
"title": "Big Data Concepts",
@@ -1360,7 +1412,7 @@
"description": "Hadoop is a critical element in the realm of data processing frameworks, offering an effective solution for storing, managing, and analyzing massive amounts of data. Unraveling meaningful insights from a large deluge of data is a challenging pursuit faced by many data analysts. Regular data processing tools fail to handle large-scale data, paving the way for advanced frameworks like Hadoop. This open-source platform by Apache Software Foundation excels at storing and processing vast data across clusters of computers. Notably, Hadoop comprises two key modules - the Hadoop Distributed File System (HDFS) for storage and MapReduce for processing. Hadoops ability to handle both structured and unstructured data further broadens its capacity. For any data analyst, a thorough understanding of Hadoop can unlock powerful ways to manage data effectively and construct meaningful analytics.\n\nLearn more from the following resources:",
"links": [
{
"title": "Apache Hadoop Website",
"title": "Apache Hadoop",
"url": "https://hadoop.apache.org/",
"type": "article"
},
@@ -1381,7 +1433,7 @@
"type": "opensource"
},
{
"title": "Apache Spark Website",
"title": "Apache Spark",
"url": "https://spark.apache.org/",
"type": "article"
}
@@ -1421,15 +1473,21 @@
},
"SiYUdtYMDImRPmV2_XPkH": {
"title": "Deep Learning (Optional)",
"description": "Deep learning, a subset of machine learning technique, is increasingly becoming a critical tool for data analysts. Deep learning algorithms utilize multiple layers of neural networks to understand and interpret intricate structures in large data, a skill that is integral to the daily functions of a data analyst. With the ability to learn from unstructured or unlabeled data, deep learning opens a whole new range of possibilities for data analysts in terms of data processing, prediction, and categorization. It has applications in a variety of industries from healthcare to finance to e-commerce and beyond. A deeper understanding of deep learning methodologies can augment a data analyst's capability to evaluate and interpret complex datasets and provide valuable insights for decision making.",
"links": []
"description": "Deep learning, a subset of machine learning technique, is increasingly becoming a critical tool for data analysts. Deep learning algorithms utilize multiple layers of neural networks to understand and interpret intricate structures in large data, a skill that is integral to the daily functions of a data analyst. With the ability to learn from unstructured or unlabeled data, deep learning opens a whole new range of possibilities for data analysts in terms of data processing, prediction, and categorization. It has applications in a variety of industries from healthcare to finance to e-commerce and beyond. A deeper understanding of deep learning methodologies can augment a data analyst's capability to evaluate and interpret complex datasets and provide valuable insights for decision making.\n\nLearn more from the following resources:",
"links": [
{
"title": "Deep Learning for Data Analysis",
"url": "https://www.ibm.com/think/topics/deep-learning",
"type": "article"
}
]
},
"gGHsKcS92StK5FolzmVvm": {
"title": "Neural Networks",
"description": "Neural Networks play a pivotal role in the landscape of deep learning, offering a plethora of benefits and applications for data analysts. They are computational models that emulate the way human brain processes information, enabling machines to make intelligent decisions. As a data analyst, understanding and utilizing neural networks can greatly enhance decision-making process as it allows to quickly and effectively analyze large datasets, recognize patterns, and forecast future trends. In deep learning, these networks are used for creating advanced models that can tackle complex tasks such as image recognition, natural language processing, and speech recognition, to name but a few. Therefore, an in-depth knowledge of neural networks is a significant asset for any aspiring or professional data analyst.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a neural network?",
"title": "What is a Neural Network?",
"url": "https://aws.amazon.com/what-is/neural-network/",
"type": "article"
},
@@ -1461,12 +1519,12 @@
"description": "Recurrent Neural Networks(RNNs) are a type of Artificial Neural Networks(ANNs) which introduces us to the realm of Deep Learning, an aspect that has been significantly contributing to the evolution of Data Analysis. RNNs are specifically designed to recognize patterns in sequences of data, such as text, genomes, handwriting, or the spoken word. This inherent feature of RNNs makes them extremely useful and versatile for a data analyst.\n\nA data analyst leveraging RNNs can effectively charter the intrinsic complexity of data sequences, classify them, and make accurate predictions. With the fundamental understanding of deep learning, data analysts can unlock the full potential of RNNs in delivering insightful data analysis that goes beyond traditional statistical methods. Modern research and applications of RNNs extend to multiple domains including natural language processing, speech recognition, and even in the financial sphere for stock price prediction making this a key tool in a data analysts arsenal.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a recurrent neural network (RNN)?",
"title": "What is a Recurrent Neural Network (RNN)?",
"url": "https://www.ibm.com/topics/recurrent-neural-networks",
"type": "article"
},
{
"title": "Recurrent Neural Networks cheatsheet",
"title": "Recurrent Neural Networks Cheat-sheet",
"url": "https://stanford.edu/~shervine/teaching/cs-230/cheatsheet-recurrent-neural-networks",
"type": "article"
}
@@ -1477,10 +1535,15 @@
"description": "TensorFlow, developed by Google Brain Team, has become a crucial tool in the realm of data analytics, particularly within the field of deep learning. It's an open-source platform for machine learning, offering a comprehensive and flexible ecosystem of tools, libraries, and community resources. As a data analyst, understanding and implementing TensorFlow for deep learning models allows us to identify complex patterns and make insightful predictions which standard analysis could miss. It's in-demand skill that enhances our ability to generate accurate insights from colossal and complicated structured or unstructured data sets.\n\nLearn more from the following resources:",
"links": [
{
"title": "Tensorflow Website",
"title": "Tensorflow",
"url": "https://www.tensorflow.org/",
"type": "article"
},
{
"title": "Tensorflow Documentation",
"url": "https://www.tensorflow.org/learn",
"type": "article"
},
{
"title": "Tensorflow in 100 seconds",
"url": "https://www.youtube.com/watch?v=i8NETqtGHms",
@@ -1493,10 +1556,15 @@
"description": "PyTorch, an open-source machine learning library, has gained considerable popularity among data analysts due to its simplicity and high performance in tasks such as natural language processing and artificial intelligence. Specifically, in the domain of deep learning, PyTorch stands out due to its dynamic computational graph, allowing for a highly intuitive and flexible platform for building complex models. For data analysts, mastering PyTorch can open up a broad range of opportunities for data model development, data processing, and integration of machine learning algorithms.\n\nLearn more from the following resources:",
"links": [
{
"title": "PyTorch Website",
"title": "PyTorch",
"url": "https://pytorch.org/",
"type": "article"
},
{
"title": "PyTorch Documentation",
"url": "https://pytorch.org/docs/stable/index.html",
"type": "article"
},
{
"title": "PyTorch in 100 seconds",
"url": "https://www.youtube.com/watch?v=ORMx45xqWkA",
@@ -1509,7 +1577,7 @@
"description": "Image Recognition has become a significant domain because of its diverse applications, including facial recognition, object detection, character recognition, and much more. As a Data Analyst, understanding Image Recognition under Deep Learning becomes crucial. The data analyst's role in this context involves deciphering complex patterns and extracting valuable information from image data. This area of machine learning combines knowledge of data analysis, image processing, and deep neural networks to provide accurate results, contributing significantly to the progression of fields like autonomous vehicles, medical imaging, surveillance, among others. Therefore, proficiency in this field paves the way for proficient data analysis, leading to innovative solutions and improved decision-making.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is image recognition?",
"title": "What is Image Recognition?",
"url": "https://www.techtarget.com/searchenterpriseai/definition/image-recognition",
"type": "article"
},
@@ -1541,12 +1609,12 @@
"description": "As a business enterprise expands, so does its data. For data analysts, the surge in information means they need efficient and scalable data storage solutions to manage vast volumes of structured and unstructured data, collectively referred to as Big Data. Big Data storage solutions are critical in preserving the integrity of data while also providing quick and easy access to the data when needed. These solutions use software and hardware components to securely store massive amounts of information across numerous servers, allowing data analysts to perform robust data extraction, data processing and complex data analyses. There are several options, from the traditional Relational Database Management Systems (RDBMS) to the more recent NoSQL databases, Hadoop ecosystems, and Cloud storage solutions, each offering unique capabilities and benefits to cater for different big data needs.\n\nLearn more from the following resources:",
"links": [
{
"title": "SQL Roadmap",
"title": "Visit Dedicated SQL Roadmap",
"url": "https://roadmap.sh/sql",
"type": "article"
},
{
"title": "PostgreSQL Roadmap",
"title": "Visit Dedicated PostgreSQL Roadmap",
"url": "https://roadmap.sh/postgresql-dba",
"type": "article"
}

View File

@@ -3026,14 +3026,14 @@
"title": "Envoy",
"description": "Originally created at Lyft, Envoy is a high-performance data plane designed for service mesh architectures. Lyft open sourced it and donated it to the CNCF, where it is now one of the CNCFs graduated open source projects. Envoy is a self contained process that is designed to run alongside every application server. All of the Envoys form a transparent communication mesh in which each application sends and receives messages to and from localhost and is unaware of the network topology.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Envoy Website",
"url": "https://www.envoyproxy.io/",
"type": "article"
},
{
"title": "envoyproxy/envoy",
"url": "https://github.com/envoyproxy/envoy",
"type": "opensource"
},
{
"title": "Envoy",
"url": "https://www.envoyproxy.io/",
"type": "article"
},
{

View File

@@ -162,8 +162,14 @@
},
"7PBmYoSmIgZT21a2Ip3_S": {
"title": "Trust / Influence Building",
"description": "Building trust and influence is crucial for any Engineering Manager. This involves establishing a solid reputation, delivering on promises and being an active listener to your team's ideas and issues. It's a manager's job to ensure there's an open, honest environment that promotes trust. Balancing delegation and taking charge, especially in difficult situations, is key to building influence.\n\nOne challenge in this area is building trust between team members of varying experiences and skills. Managers must not only show the team they're competent, but also that they value everyone's inputs. They can achieve this by promoting inclusivity and praising team contributions regularly.\n\nBeing patient, communicate clearly, and showing empathy are critical skills that can help an Engineering Manager in trust and influence building. By embodying these traits, managers can build a stronger, united, and more effective engineering team.",
"links": []
"description": "Building trust and influence is crucial for any Engineering Manager. This involves establishing a solid reputation, delivering on promises and being an active listener to your team's ideas and issues. It's a manager's job to ensure there's an open, honest environment that promotes trust. Balancing delegation and taking charge, especially in difficult situations, is key to building influence.\n\nOne challenge in this area is building trust between team members of varying experiences and skills. Managers must not only show the team they're competent, but also that they value everyone's inputs. They can achieve this by promoting inclusivity and praising team contributions regularly.\n\nBeing patient, communicate clearly, and showing empathy are critical skills that can help an Engineering Manager in trust and influence building. By embodying these traits, managers can build a stronger, united, and more effective engineering team.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Understanding The Trust Equation",
"url": "https://trustedadvisor.com/why-trust-matters/understanding-trust/understanding-the-trust-equation",
"type": "article"
}
]
},
"b3qoH_LuW-Gz4N8WdGnZs": {
"title": "One-on-One Meetings",

View File

@@ -170,6 +170,11 @@
"title": "HTML",
"description": "HTML (Hypertext Markup Language) is the standard markup language used to create web pages and web applications. It provides a structure for content on the World Wide Web, using a system of elements and attributes to define the layout and content of a document. HTML elements are represented by tags, which browsers interpret to render the visual and auditory elements of a web page. The language has evolved through several versions, with HTML5 being the current standard, introducing semantic elements, improved multimedia support, and enhanced form controls. HTML works in conjunction with CSS for styling and JavaScript for interactivity, forming the foundation of modern web development.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Responsive Web Design Certification - Co-Learn HTML & CSS with guided projects",
"url": "https://www.freecodecamp.org/learn/2022/responsive-web-design/",
"type": "course"
},
{
"title": "W3Schools: Learn HTML",
"url": "https://www.w3schools.com/html/html_intro.asp",
@@ -321,6 +326,11 @@
"url": "https://www.youtube.com/watch?v=G3e-cpL7ofc",
"type": "course"
},
{
"title": "Responsive Web Design Certification - Co-Learn HTML & CSS with guided projects",
"url": "https://www.freecodecamp.org/learn/2022/responsive-web-design/",
"type": "course"
},
{
"title": "Web.dev by Google — Learn CSS",
"url": "https://web.dev/learn/css/",

View File

@@ -364,8 +364,13 @@
"description": "Joints in game development primarily refer to the connections between two objects, often used in the context of physics simulations and character animations. These might simulate the physics of real-world joints like hinges or springs. Developers can control various characteristics of joints such as their constraints, forces, and reactions. The different types come with various properties suitable for specific needs. For example, Fixed joints keep objects together, Hinge joints allow rotation around an axis, and Spring joints apply a force to keep objects apart.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Game Character Rigging Fundamentals",
"url": "https://learn.unity.com/project/game-character-rigging-fundamentals",
"title": "Introduction to joints",
"url": "https://docs.unity3d.com/Manual/Joints.html",
"type": "article"
},
{
"title": "Character Rigging for Video Games",
"url": "https://game-ace.com/blog/character-rigging-for-video-games/",
"type": "article"
},
{
@@ -599,16 +604,16 @@
},
"vWLKYK2KUzV1fO-vQunzW": {
"title": "EPA",
"description": "The **EPA**, also known as the _Environmental Protection Agency_, is not typically related to game development or the concept of intersection within this context. However, in game development, EPA might refer to an 'Event-driven Process chain Architecture' or some other game-specific acronym. In this domain, different terminologies and acronyms are often used to express complex architectures, designs, or functionalities. If you have encountered EPA in a game development context, it might be best to refer to the specific documentation or guide where it was described for a better understanding. Understanding the context is key to untangle the meaning of such abbreviations.\n\nVisit the following resources to learn more:",
"description": "The **EPA** (Expanding Polytope Algorithm) is an iterative algorithm used for calculating the penetration depth between two shapes in collision detection. It is commonly used in physics engines and robotics. The algorithm takes the resulting simplex from a previously applied GJK algorithm, iteratively expanding the polytope towards the Minkowski Difference boundary until it finds the closest point to the origin. The vector from that point to the origin is the penetration vector and its magnitude is equal to the penetration depth between the two shapes.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Environmental Sustainability in Game Development",
"url": "https://polydin.com/environmental-sustainability-in-game-development/",
"title": "EPA: Collision response algorithm for 2D/3D - winter.dev",
"url": "https://winter.dev/articles/epa-algorithm",
"type": "article"
},
{
"title": "Gaming Sustainability - Microsoft Game Dev",
"url": "https://learn.microsoft.com/en-us/gaming/sustainability/sustainability-overview",
"title": "EPA (Expanding Polytope Algorithm) - dyn4j",
"url": "https://dyn4j.org/2010/05/epa-expanding-polytope-algorithm/",
"type": "article"
}
]
@@ -744,7 +749,7 @@
},
"7OffO2mBmfBKqPBTZ9ngI": {
"title": "Godot",
"description": "Godot is an open-source, multi-platform game engine that is known for being feature-rich and user-friendly. It is developed by hundreds of contributors from around the world and supports the creation of both 2D and 3D games. Godot uses its own scripting language, GDScript, which is similar to Python, but it also supports C# and visual scripting. It is equipped with a unique scene system and comes with a multitude of tools that can expedite the development process. Godot's design philosophy centers around flexibility, extensibility, and ease of use, providing a handy tool for both beginners and pros in game development.\n\nVisit the following resources to learn more:",
"description": "Godot is an open-source, multi-platform game engine that is known for being feature-rich and user-friendly. It is developed by hundreds of contributors from around the world and supports the creation of both 2D and 3D games. Godot uses its own scripting language, GDScript, which is similar to Python, but it also supports C#. It is equipped with a unique scene system and comes with a multitude of tools that can expedite the development process. Godot's design philosophy centers around flexibility, extensibility, and ease of use, providing a handy tool for both beginners and pros in game development.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "godotengine/godot",
@@ -763,7 +768,17 @@
},
{
"title": "Godot in 100 Seconds",
"url": "https://m.youtube.com/watch?v=QKgTZWbwD1U",
"url": "https://www.youtube.com/watch?v=QKgTZWbwD1U",
"type": "video"
},
{
"title": "Tutorial - How to make a Video Game in Godot",
"url": "https://www.youtube.com/watch?v=LOhfqjmasi0",
"type": "video"
},
{
"title": "Tutorial - How to make 3D Games in Godot",
"url": "https://www.youtube.com/watch?v=ke5KpqcoiIU",
"type": "video"
}
]
@@ -882,8 +897,8 @@
"description": "**C** and **C++ (commonly known as CPP)** are two of the most foundational high-level programming languages in computer science. **C** was developed in the 1970s and it is a procedural language, meaning it follows a step-by-step approach. Its fundamental principles include structured programming and lexical variable scope.\n\nOn the other hand, **C++** follows the paradigm of both procedural and object-oriented programming. It was developed as an extension to C to add the concept of \"classes\" - a core feature of object-oriented programming. C++ enhances C by introducing new features like function overloading, exception handling, and templates.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "The C Programming Language",
"url": "https://www.iso.org/standard/74528.html",
"title": "C Programming Language",
"url": "https://en.wikipedia.org/wiki/C_%28programming_language%29",
"type": "article"
},
{
@@ -971,11 +986,6 @@
"title": "Computer Graphics",
"description": "Computer Graphics is a subfield of computer science that studies methods for digitally synthesizing and manipulating visual content. It involves creating and manipulating visual content using specialized computer software and hardware. This field is primarily used in the creation of digital and video games, CGI in films, and also in visual effects for commercials. The field is divided into two major categories: **Raster graphics** and **Vector graphics**. Raster graphics, also known as bitmap, involve the representation of images through a dot matrix data structure, while Vector graphics involve the use of polygons to represent images in computer graphics. Both of these methods have their unique usage scenarios. Other concepts integral to the study of computer graphics include rendering (including both real-time rendering and offline rendering), animation, and 3D modeling. Generally, computer graphics skills are essential for game developers and animation experts.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "What is Computer Graphics?",
"url": "https://www.geeksforgeeks.org/introduction-to-computer-graphics/",
"type": "article"
},
{
"title": "Introduction to Computer Graphics",
"url": "https://open.umn.edu/opentextbooks/textbooks/420",
@@ -1881,11 +1891,6 @@
"title": "MCTS Algorithm",
"url": "https://en.wikipedia.org/wiki/Monte_Carlo_tree_search/",
"type": "article"
},
{
"title": "Monte Carlo Tree Search",
"url": "https://www.geeksforgeeks.org/ml-monte-carlo-tree-search-mcts/",
"type": "article"
}
]
},
@@ -1983,11 +1988,6 @@
"title": "Artificial Neural Network",
"description": "Artificial Neural Networks (ANN) are a branch of machine learning that draw inspiration from biological neural networks. ANNs are capable of 'learning' from observational data, thereby enhancing game development in numerous ways. They consist of interconnected layers of nodes, or artificial neurons, that process information through their interconnected network. Each node's connection has numerical weight that gets adjusted during learning, which helps in optimizing problem solving. ANNs are utilized in various aspects of game development, such as improving AI behavior, procedural content generation, and game testing. They can also be used for image recognition tasks, such as identifying objects or actions in a game environment.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Artificial Neural Networks (ANN)",
"url": "https://www.geeksforgeeks.org/artificial-neural-networks-and-its-applications/",
"type": "article"
},
{
"title": "What is ANN?",
"url": "https://www.coursera.org/articles/artificial-neural-network",

View File

@@ -670,11 +670,6 @@
"url": "https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-outside-collaborators/adding-outside-collaborators-to-repositories-in-your-organization",
"type": "article"
},
{
"title": "What are github collaborators",
"url": "https://www.geeksforgeeks.org/what-are-github-collaborators/",
"type": "article"
},
{
"title": "How to Add Collaborators to Your GitHub Repository",
"url": "https://www.blinkops.com/blog/how-to-add-collaborators-to-your-github-repository",
@@ -1440,7 +1435,7 @@
},
"uxqJzQFRcALqatNRIWR0w": {
"title": "Unstaged Changes",
"description": "For changes that are not yet staged with `git add`, such as untracked new files or modified existing ones, use `git diff --unified`. This command compares your working directory against the latest committed version of each file. It's a useful tool for reviewing any local modifications before deciding whether to stage them for future commits.",
"description": "For changes that are not yet staged with `git add`, such as untracked new files or modified existing ones , use `git diff`. This command compares your working directory (your current changes) against the staging area (changes already staged with `git add`). Its a useful tool for reviewing local modifications before deciding whether to stage them for future commits.\n\nThe `--unified` option (or -U) controls the number of context lines shown in the diff output. By default, Git shows 3 lines of context around each change. For example, `git diff --unified=5` will display 5 lines of context around each change, making it easier to understand the surrounding code or content.",
"links": [
{
"title": "What are unstaged changes in GitHub?",

File diff suppressed because it is too large Load Diff

View File

@@ -334,11 +334,6 @@
"title": "JavaScript Scope",
"url": "https://www.w3schools.com/js/js_scope.asp",
"type": "article"
},
{
"title": "Block Scoping in JavaScript",
"url": "https://www.geeksforgeeks.org/javascript-es2015-block-scoping",
"type": "article"
}
]
},

View File

@@ -1023,16 +1023,16 @@
},
"XteNExIZN3_g95_dPCopY": {
"title": "Exitting / Exit Codes",
"description": "`Exiting` is a way of terminating a Node.js process by using node.js process module.\n\nVisit the following resources to learn more:",
"description": "Exiting is a way of terminating a Node.js process by using node.js process module.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Node.js Docs on exit",
"url": "https://nodejs.org/docs/latest/api/process.html",
"title": "Exit Documentation",
"url": "https://nodejs.org/api/process.html#event-exit",
"type": "article"
},
{
"title": "How to Exit a Process in Node.js",
"url": "https://www.knowledgehut.com/blog/web-development/node-js-process-exit",
"url": "https://betterstack.com/community/questions/how-to-exit-in-node-js/",
"type": "article"
}
]

View File

@@ -319,8 +319,13 @@
"description": "The Null Safe Operator is a handy feature in PHP which deals with an issue that often pops up when working with objects: trying to access properties or methods on an object that might be null. Instead of a fatal error, the PHP Null Safe Operator (indicated by ?->) allows null values to be returned safely, making your code more robust. Here's a quick example, consider $session?->user?->name. If $session or user is null, PHP will stop further execution and simply return null. This makes PHP more resilient when processing unpredictable data.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Null Safe Operator",
"url": "https://www.php.net/manual/en/language.oop5.nullsafe.php",
"title": "The Basics - Manual",
"url": "https://www.php.net/manual/en/language.oop5.basic.php",
"type": "article"
},
{
"title": "PHP RFC: Nullsafe operator",
"url": "https://wiki.php.net/rfc/nullsafe_operator",
"type": "article"
}
]
@@ -393,7 +398,7 @@
},
"RkNjYva8o_jXp9suz5YdG": {
"title": "Named Arguments",
"description": "Named arguments in PHP, introduced with PHP 8.0, allow you to specify the values of required parameters by their names, instead of their position in the function call, thus making your code more readable, reducing mistakes, and allowing for unimportant arguments to be skipped. Here's an array\\_fill() function using named arguments:\n\n <?php\n $a = array_fill(start_index: 0, num: 100, value: 50);\n \n\nIn this code snippet, the parameters are passed by their names ('start\\_index', 'num', 'value'), not by their order in the function definition.\n\nVisit the following resources to learn more:",
"description": "Named arguments in PHP, introduced with PHP 8.0, allow you to specify the values of required parameters by their names, instead of their position in the function call, thus making your code more readable, reducing mistakes, and allowing for unimportant arguments to be skipped. Here's an array\\_fill() function using named arguments:\n\n <?php\n $a = array_fill(start_index: 0, count: 100, value: 50);\n \n\nIn this code snippet, the parameters are passed by their names ('start\\_index', 'count', 'value'), not by their order in the function definition.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Named Arguments",
@@ -712,14 +717,8 @@
},
"J9yIXZTtwbFzH2u4dI1ep": {
"title": "CSRF Protection",
"description": "Cross-Site Request Forgery (CSRF) Protection in PHP is a method where a website can defend itself against unwanted actions performed on behalf of the users without their consent. It's a critical aspect of security as it safeguards users against potential harmful activities. Here's an example: if users are logged into a website and get tricked into clicking a deceitful link, CSRF attacks could be triggered. To protect your PHP applications from such attacks, you can generate a unique token for every session and include it as a hidden field for all form submissions. Afterwards, you need to verify this token on the server side before performing any action.\n\n <?php\n // Generate CSRF token\n if(empty($_SESSION['csrf'])) {\n $_SESSION['csrf'] = bin2hex(random_bytes(32));\n }\n \n // Verify CSRF token\n if(isset($_POST['csrf']) && $_POST['csrf'] === $_SESSION['csrf']) {\n // valid CSRF token, perform action\n }\n ?>\n \n\nVisit the following resources to learn more:",
"links": [
{
"title": "Security Guide",
"url": "https://php.net/manual/en/security.csrf.php",
"type": "article"
}
]
"description": "Cross-Site Request Forgery (CSRF) Protection in PHP is a method where a website can defend itself against unwanted actions performed on behalf of the users without their consent. It's a critical aspect of security as it safeguards users against potential harmful activities. Here's an example: if users are logged into a website and get tricked into clicking a deceitful link, CSRF attacks could be triggered. To protect your PHP applications from such attacks, you can generate a unique token for every session and include it as a hidden field for all form submissions. Afterwards, you need to verify this token on the server side before performing any action.\n\n <?php\n // Generate CSRF token\n if(empty($_SESSION['csrf'])) {\n $_SESSION['csrf'] = bin2hex(random_bytes(32));\n }\n \n // Verify CSRF token\n if(isset($_POST['csrf']) && $_POST['csrf'] === $_SESSION['csrf']) {\n // valid CSRF token, perform action\n }\n ?>\n \n\nVisit the following resources to learn more:\n\n* \\[@article@PHP Tutorial CSRF\\] ([https://www.phptutorial.net/php-tutorial/php-csrf/](https://www.phptutorial.net/php-tutorial/php-csrf/))",
"links": []
},
"JbWFfJiCRrXDhnuIx_lqx": {
"title": "Password Hashing",
@@ -1021,12 +1020,12 @@
"description": "Symfony is a set of PHP components and a framework for web projects. It aims to speed up the creation and maintenance of web applications and replace the recurring coding tasks. Symfony uses Composer, a PHP dependency manager, to manage its components. Below is an example of creating a new Symfony project:\n\n composer create-project symfony/website-skeleton myproject\n \n\nThis will download and install a new Symfony project in the 'myproject' directory. Symfony's components are reusable PHP libraries that will help you complete tasks, like routing, templating, or even creating form handling.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Symphony",
"title": "Symfony",
"url": "https://symfony.com/",
"type": "article"
},
{
"title": "Symphony Documentation",
"title": "Symfony Documentation",
"url": "https://symfony.com/doc/current/index.html",
"type": "article"
}

View File

@@ -251,11 +251,6 @@
"title": "What is the Relational Model?",
"url": "https://www.postgresql.org/docs/7.1/relmodel-oper.html",
"type": "article"
},
{
"title": "The Relational Model",
"url": "https://www.geeksforgeeks.org/relational-model-in-dbms/",
"type": "article"
}
]
},
@@ -583,7 +578,7 @@
"links": [
{
"title": "pg_ctlcluster",
"url": "https://www.postgresql.org/docs/current/pgctlcluster.html",
"url": "https://manpages.ubuntu.com/manpages/focal/man1/pg_ctlcluster.1.html",
"type": "article"
}
]
@@ -1075,7 +1070,7 @@
]
},
"S20aJB-VuSpXYyd0-0S8c": {
"title": "Object Priviliges",
"title": "Object Privileges",
"description": "Object privileges in PostgreSQL are the permissions given to different user roles to access or modify database objects like tables, views, sequences, and functions. Ensuring proper object privileges is crucial for maintaining a secure and well-functioning database.\n\nLearn more from the following resources:",
"links": [
{
@@ -1117,7 +1112,7 @@
]
},
"t18XjeHP4uRyERdqhHpl5": {
"title": "Default Priviliges",
"title": "Default Privileges",
"description": "PostgreSQL allows you to define object privileges for various types of database objects. These privileges determine if a user can access and manipulate objects like tables, views, sequences, or functions. In this section, we will focus on understanding default privileges in PostgreSQL.\n\nLearn more from the following resources:",
"links": [
{

View File

@@ -399,8 +399,19 @@
},
"gS3ofDrqDRKbecIskIyGi": {
"title": "Product Roadmap",
"description": "The product roadmap is a strategic document that provides a detailed overview of the product's direction and vision. It outlines the product's plans, both tactical and strategic - including the specific steps necessary to achieve the company's goals and vision. As a Product Manager, you are expected to guide the creation of the product roadmap, communicating the products evolution to the team, stakeholders, and customers. This tool serves as an essential reference point helping to align all stakeholders with the key priorities and vision of the product, and acts as a guide for decisions around product development.",
"links": []
"description": "The product roadmap is a strategic document that provides a detailed overview of the product's direction and vision. It outlines the product's plans, both tactical and strategic - including the specific steps necessary to achieve the company's goals and vision. As a Product Manager, you are expected to guide the creation of the product roadmap, communicating the products evolution to the team, stakeholders, and customers. This tool serves as an essential reference point helping to align all stakeholders with the key priorities and vision of the product, and acts as a guide for decisions around product development.\n\nLearn more from the following resources:",
"links": [
{
"title": "What is a Product Roadmap? - Product Plan",
"url": "https://www.productplan.com/learn/what-is-a-product-roadmap/",
"type": "article"
},
{
"title": "What is a Product Roadmap? - Vibhor Chandel",
"url": "https://www.youtube.com/watch?v=BJR70jnpHog&ab_channel=VibhorChandel",
"type": "video"
}
]
},
"eiqV86PWizZPWsyqoBU5k": {
"title": "Creating a Roadmap",

View File

@@ -179,6 +179,21 @@
"title": "Tuples Documentation",
"url": "https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences",
"type": "article"
},
{
"title": "When and How to Use Tuples",
"url": "https://thenewstack.io/python-for-beginners-when-and-how-to-use-tuples/",
"type": "article"
},
{
"title": "Python's tuple Data Type: A Deep Dive With Examples",
"url": "https://realpython.com/python-tuple/#getting-started-with-pythons-tuple-data-type",
"type": "article"
},
{
"title": "why are Tuples even a thing?",
"url": "https://www.youtube.com/watch?v=fR_D_KIAYrE",
"type": "video"
}
]
},
@@ -205,7 +220,7 @@
},
"bc9CL_HMT-R6nXO1eR-gP": {
"title": "Dictionaries",
"description": "In Python, a dictionary is a built-in data type that allows you to store key-value pairs. Each key in the dictionary is unique, and each key is associated with a value. Dictionaries are unordered collections, meaning the order of items is not guaranteed.\n\nLearn more from the following resources:",
"description": "In Python, a dictionary is a built-in data type that allows you to store key-value pairs. Each key in the dictionary is unique, and each key is associated with a value. Starting from Python 3.7, dictionaries maintain the order of items as they were added.\n\nLearn more from the following resources:",
"links": [
{
"title": "Dictionaries in Python",
@@ -216,6 +231,11 @@
"title": "W3 Schools - Dictionaries",
"url": "https://www.w3schools.com/python/python_dictionaries.asp",
"type": "article"
},
{
"title": "Dictionaries in Python",
"url": "https://realpython.com/python-dicts/",
"type": "article"
}
]
},
@@ -223,11 +243,6 @@
"title": "Loops",
"description": "Loops are used to execute a block of code repeatedly.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Loops in Python",
"url": "https://www.geeksforgeeks.org/loops-in-python/",
"type": "article"
},
{
"title": "Python \"while\" Loops (Indefinite Iteration)",
"url": "https://realpython.com/python-while-loop/",
@@ -533,6 +548,11 @@
"title": "Modules in Python",
"url": "https://www.programiz.com/python-programming/modules",
"type": "article"
},
{
"title": "Python Modules and Packages",
"url": "https://realpython.com/python-modules-packages/",
"type": "article"
}
]
},
@@ -596,6 +616,11 @@
"title": "Python Iterators",
"url": "https://www.programiz.com/python-programming/iterator",
"type": "article"
},
{
"title": "Iterators and Iterables in Python",
"url": "https://realpython.com/python-iterators-iterables/",
"type": "article"
}
]
},
@@ -622,7 +647,7 @@
},
"P_Di-XPSDITmU3xKQew8G": {
"title": "Object Oriented Programming",
"description": "In Python, object-oriented Programming (OOPs) is a programming paradigm that uses objects and classes in programming. It aims to implement real-world entities like inheritance, polymorphisms, encapsulation, etc. in the programming. The main concept of OOPs is to bind the data and the functions that work on that together as a single unit so that no other part of the code can access this data.\n\nVisit the following resources to learn more:",
"description": "In Python, object-oriented Programming (OOPs) is a programming paradigm that uses objects and classes in programming. It aims to implement real-world entities like inheritance, polymorphism, encapsulation, etc., in programming. The main concept of OOPs is to bind the data and the functions that work on that together as a single unit so that no other part of the code can access this data.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Object Oriented Programming in Python",
@@ -685,7 +710,7 @@
},
"zAS4YiEJ6VPsyABrkIG8i": {
"title": "Methods, Dunder",
"description": "A method in python is somewhat similar to a function, except it is associated with object/classes. Methods in python are very similar to functions except for two major differences.\n\n* The method is implicitly used for an object for which it is called.\n* The method is accessible to data that is contained within the class.\n\nDunder or magic methods in Python are the methods having two prefix and suffix underscores in the method name. Dunder here means “Double Under (Underscores)”. These are commonly used for operator overloading. Few examples for magic methods are: **`__init__`**, **`__add__`**, **`__len__`**, **`__repr__`** etc.\n\nVisit the following resources to learn more:",
"description": "A method in python is somewhat similar to a function, except it is associated with object/classes. Methods in python are very similar to functions except for two major differences.\n\n* The method is implicitly used for an object for which it is called.\n* The method is accessible to data that is contained within the class.\n\nDunder or magic methods in Python are the methods that have two prefix and suffix underscores in the method name. Dunder here means “Double Under (Underscores)”. These are commonly used for operator overloading. Few examples for magic methods are: **`__init__`**, **`__add__`**, **`__len__`**, **`__repr__`** etc.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Method vs Function in Python",
@@ -1284,7 +1309,7 @@
"description": "An extremely fast Python linter and code formatter, written in Rust.",
"links": [
{
"title": "ruff documentation",
"title": "Ruff documentation",
"url": "https://docs.astral.sh/ruff/",
"type": "article"
}

View File

@@ -534,7 +534,7 @@
"links": [
{
"title": "RPOP Documentation",
"url": "https://redis.io/docs/latest/commands/rpush/",
"url": "https://redis.io/docs/latest/commands/rpop/",
"type": "article"
}
]
@@ -732,11 +732,6 @@
"title": "Redis Lists",
"url": "https://redis.io/docs/latest/develop/data-types/lists/",
"type": "article"
},
{
"title": "Complete Guide to Redis Lists",
"url": "https://www.geeksforgeeks.org/complete-guide-to-redis-lists/",
"type": "article"
}
]
},
@@ -954,8 +949,19 @@
},
"jrgaoDnt_RxTu79hk4hCD": {
"title": "Atomicity in Redis",
"description": "Atomicity in Redis refers to the property that ensures a set of operations is executed as a single, indivisible unit. This means that either all the operations are executed successfully or none of them are. Atomicity is crucial in Redis to maintain consistency, especially when multiple operations need to be performed together.\n\nLearn more from the following resources:\n\n* [@official@Atomicity with Lua](https://redis.io/learn/develop/java/spring/rate-limiting/fixed-window/reactive-lua) -[@article@Atomicity in Redis operations](https://lucaspin.medium.com/atomicity-in-redis-operations-a1d7bc9f4a90)",
"links": []
"description": "Atomicity in Redis refers to the property that ensures a set of operations is executed as a single, indivisible unit. This means that either all the operations are executed successfully or none of them are. Atomicity is crucial in Redis to maintain consistency, especially when multiple operations need to be performed together.\n\nLearn more from the following resources:",
"links": [
{
"title": "Atomicity with Lua",
"url": "https://redis.io/learn/develop/java/spring/rate-limiting/fixed-window/reactive-lua",
"type": "article"
},
{
"title": "Atomicity in Redis operations",
"url": "https://lucaspin.medium.com/atomicity-in-redis-operations-a1d7bc9f4a90",
"type": "article"
}
]
},
"LHlwjN3WHYUBUafzzwsWQ": {
"title": "Pipelining",
@@ -965,11 +971,6 @@
"title": "Redis Pipelining",
"url": "https://redis.io/docs/latest/develop/use/pipelining/",
"type": "article"
},
{
"title": "Complete Guide to Redis Pipelining",
"url": "https://www.geeksforgeeks.org/complete-guide-to-redis-pipelining/",
"type": "article"
}
]
},

View File

@@ -257,7 +257,18 @@
"E4H3hniIW6hKpH3Qr--N5": {
"title": "C/C++",
"description": "\"C\" and \"C++\", often written as \"C/CPP\", are two significantly prominent and similar programming languages widely used in server-side game development. \"C\" is a procedural language, which means that it follows a step-by-step procedure to solve a problem, while \"C++\" is both a procedural and object-oriented programming (OOP) language. This dual nature of \"C++\" allows it to handle more complex interrelated data and functions efficiently, which is a beneficial feature in game development. Moreover, \"C++\" is an extension of \"C\", meaning that any legal \"C\" program is also a valid \"C++\" program. Both languages offer a high degree of control over system resources and memory, making them an excellent choice for building fast and efficient server-side applications, such as multiplayer game servers.",
"links": []
"links": [
{
"title": "C Programming Language",
"url": "https://en.wikipedia.org/wiki/C_%28programming_language%29",
"type": "article"
},
{
"title": "C++ Programming Language",
"url": "https://en.wikipedia.org/wiki/C%2B%2B",
"type": "article"
}
]
},
"DuyUc9a-47Uz03yr4aeyg": {
"title": "C#",

View File

@@ -38,14 +38,8 @@
},
"2sR4KULvAUUoOtopvsEBs": {
"title": "Levels of Architecture",
"description": "Architecture can be done on several “levels” of abstractions. The level influences the importance of necessary skills. As there are many categorizations possible my favorite segmentation includes these 3 levels:\n\n* **Application Level:** The lowest level of architecture. Focus on one single application. Very detailed, low level design. Communication is usually within one development team.\n* **Solution Level:** The mid-level of architecture. Focus on one or more applications which fulfill a business need (business solution). Some high, but mainly low-level design. Communication is between multiple development teams.\n* **Enterprise Level:** The highest level of architecture. Focus on multiple solutions. High level, abstract design, which needs to be detailed out by solution or application architects. Communication is across the organization.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Software Engineering Architecture",
"url": "https://www.geeksforgeeks.org/software-engineering-architectural-design/",
"type": "article"
}
]
"description": "Architecture can be done on several “levels” of abstractions. The level influences the importance of necessary skills. As there are many categorizations possible my favorite segmentation includes these 3 levels:\n\n* **Application Level:** The lowest level of architecture. Focus on one single application. Very detailed, low level design. Communication is usually within one development team.\n* **Solution Level:** The mid-level of architecture. Focus on one or more applications which fulfill a business need (business solution). Some high, but mainly low-level design. Communication is between multiple development teams.\n* **Enterprise Level:** The highest level of architecture. Focus on multiple solutions. High level, abstract design, which needs to be detailed out by solution or application architects. Communication is across the organization.",
"links": []
},
"Lqe47l4j-C4OwkbkwPYry": {
"title": "Application Architecture",
@@ -103,14 +97,8 @@
},
"lBtlDFPEQvQ_xtLtehU0S": {
"title": "Important Skills to Learn",
"description": "To support the laid-out activities specific skills are required. From my experience, read books and discussions we can boil this down to these ten skills every software architect should have:\n\n* Design\n* Decide\n* Simplify\n* Code\n* Document\n* Communicate\n* Estimate\n* Balance\n* Consult\n* Market\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Software Architect Skills",
"url": "https://www.geeksforgeeks.org/software-architects-skills/",
"type": "article"
}
]
"description": "To support the laid-out activities specific skills are required. From my experience, read books and discussions we can boil this down to these ten skills every software architect should have:\n\n* Design\n* Decide\n* Simplify\n* Code\n* Document\n* Communicate\n* Estimate\n* Balance\n* Consult\n* Market",
"links": []
},
"fBd2m8tMJmhuNSaakrpg4": {
"title": "Design & Architecture",
@@ -496,7 +484,7 @@
},
"_U0VoTkqM1d6NR13p5azS": {
"title": "Patterns & Design Principles",
"description": "",
"description": "In the realm of software architecture, patterns and design principles are foundational tools that enable architects to create robust, scalable, and maintainable systems. They offer proven solutions to common problems and guide decision-making throughout the software development lifecycle. Understanding these concepts is essential for anyone following a software architect roadmap, as they bridge the gap between high-level architecture and practical implementation.",
"links": []
},
"AMDLJ_Bup-AY1chl_taV3": {
@@ -854,14 +842,8 @@
},
"SuMhTyaBS9vwASxAt39DH": {
"title": "Tools",
"description": "Architect tools are software tools that help architects to design, document, and manage software architectures. These tools can be used to create architecture diagrams, generate code, and automate the software development process.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Top 10 Software Architecture Tools in 2024",
"url": "https://www.geeksforgeeks.org/software-architecture-tools/",
"type": "article"
}
]
"description": "Architect tools are software tools that help architects to design, document, and manage software architectures. These tools can be used to create architecture diagrams, generate code, and automate the software development process.",
"links": []
},
"OaLmlfkZid7hKqJ9G8oNV": {
"title": "Architecture",
@@ -1856,11 +1838,6 @@
"url": "https://www.fortinet.com/resources/cyberglossary/tcp-ip#:~:text=The%20TCP%2FIP%20model%20defines,exchanged%20and%20organized%20over%20networks.",
"type": "article"
},
{
"title": "TCP/IP Model",
"url": "https://www.geeksforgeeks.org/tcp-ip-model/",
"type": "article"
},
{
"title": "What is TCP/IP and How Does it Work?",
"url": "https://www.techtarget.com/searchnetworking/definition/TCP-IP",

View File

@@ -557,11 +557,11 @@
},
"fY8zgbB13wxZ1CFtMSdZZ": {
"title": "SQL Tuning",
"description": "SQL tuning is a broad topic and many books have been written as reference. It's important to benchmark and profile to simulate and uncover bottlenecks.\n\n* Benchmark - Simulate high-load situations with tools such as ab.\n* Profile - Enable tools such as the slow query log to help track performance issues.\n\nBenchmarking and profiling might point you to the following optimizations.\n\nTo learn more, visit the following links:",
"description": "SQL tuning is the attempt to diagnose and repair SQL statements that fail to meet a performance standard. It is a broad topic and many books have been written as reference. It's important to benchmark and profile to simulate and uncover bottlenecks.\n\n* Benchmark - Simulate high-load situations with tools such as ab.\n* Profile - Enable tools such as the slow query log to help track performance issues.\n\nBenchmarking and profiling might point you to the following optimizations.\n\nTo learn more, visit the following links:",
"links": [
{
"title": "Optimizing MySQL Queries",
"url": "https://aiddroid.com/10-tips-optimizing-mysql-queries-dont-suck/",
"title": "Introduction to SQL Tuning - Oracle",
"url": "https://docs.oracle.com/en/database/oracle/oracle-database/23/tgsql/introduction-to-sql-tuning.html#GUID-B653E5F3-F078-4BBC-9516-B892960046A2",
"type": "article"
},
{
@@ -1326,16 +1326,10 @@
}
]
},
"LncTxPg-wx8loy55r5NmV": {
"queu-based-load-leveling@LncTxPg-wx8loy55r5NmV.md": {
"title": "Queu-based Load Leveling",
"description": "Use a queue that acts as a buffer between a task and a service it invokes in order to smooth intermittent heavy loads that can cause the service to fail or the task to time out. This can help to minimize the impact of peaks in demand on availability and responsiveness for both the task and the service.\n\nLearn more from the following links:",
"links": [
{
"title": "Queue-Based Load Leveling pattern",
"url": "https://learn.microsoft.com/en-us/azure/architecture/patterns/queue-based-load-leveling",
"type": "article"
}
]
"description": "",
"links": []
},
"2ryzJhRDTo98gGgn9mAxR": {
"title": "Publisher/Subscriber",
@@ -1665,10 +1659,21 @@
}
]
},
"backends-for-frontend@n4It-lr7FFtSY83DcGydX.md": {
"n4It-lr7FFtSY83DcGydX": {
"title": "Backends for Frontend",
"description": "",
"links": []
"description": "Create separate backend services to be consumed by specific frontend applications or interfaces. This pattern is useful when you want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.\n\nTo learn more, visit the following links:",
"links": [
{
"title": "Backends for Frontends pattern",
"url": "https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends",
"type": "article"
},
{
"title": "Explore top posts about Frontend Development",
"url": "https://app.daily.dev/tags/frontend?ref=roadmapsh",
"type": "article"
}
]
},
"4hi7LvjLcv8eR6m-uk8XQ": {
"title": "Anti-Corruption Layer",

View File

@@ -652,14 +652,8 @@
},
"lvtTSHH9yBTCiLng8btnI": {
"title": "Hybrid Types",
"description": "In TypeScript, a hybrid type is a type that combines multiple types into a single type. The resulting type is considered a union of those types. This allows you to specify that a value can have multiple types, rather than just one.\n\nFor example, you can create a hybrid type that can accept either a string or a number:\n\n type StringOrNumber = string | number;\n \n\nYou can also use hybrid types to create more complex types that can represent a combination of several different types of values. For example:\n\n type Education = {\n degree: string;\n school: string;\n year: number;\n };\n \n type User = {\n name: string;\n age: number;\n email: string;\n education: Education;\n };\n \n\nLearn more from the following links:",
"links": [
{
"title": "Geeksforgeeks.org - Hybrid Types",
"url": "https://www.geeksforgeeks.org/what-are-hybrid-types-in-typescript/#:~:text=Hybrid%20types%20are%20a%20combination,properties%20like%20a%20regular%20object.",
"type": "article"
}
]
"description": "In TypeScript, a hybrid type is a type that combines multiple types into a single type. The resulting type is considered a union of those types. This allows you to specify that a value can have multiple types, rather than just one.\n\nFor example, you can create a hybrid type that can accept either a string or a number:\n\n type StringOrNumber = string | number;\n \n\nYou can also use hybrid types to create more complex types that can represent a combination of several different types of values. For example:\n\n type Education = {\n degree: string;\n school: string;\n year: number;\n };\n \n type User = {\n name: string;\n age: number;\n email: string;\n education: Education;\n };",
"links": []
},
"ib0jfZzukYOZ42AdJqt_W": {
"title": "Classes",
@@ -800,11 +794,6 @@
"title": "TypeScript Utility Types Guide",
"url": "https://camchenry.com/blog/typescript-utility-types",
"type": "article"
},
{
"title": "TypeScript Utility Types: Key Concepts And Best Practices",
"url": "https://marketsplash.com/tutorials/typescript/typescript-utility-types/",
"type": "article"
}
]
},

View File

@@ -387,8 +387,14 @@
},
"90_M5qABC1vZ1nsXVyqFJ": {
"title": "Good Layout Rules",
"description": "In the world of UX design, a good layout is crucial to ensure your prototype is intuitive and user-friendly. By following these good layout rules, you can ensure your designs are efficient, attractive, and easy to navigate for users.\n\nConsistency\n-----------\n\nBeing consistent with your design is vital in creating an easy-to-navigate interface. Utilize the same color schemes, typography, and other design elements consistently throughout your prototype to make it visually cohesive and user-friendly.\n\nAlignment and Spacing\n---------------------\n\nEnsure all the elements on your prototype are aligned and spaced properly. This helps create a well-structured and clean look, while also making it easy for users to navigate and understand your design.\n\nVisual Hierarchy\n----------------\n\nEstablish clear visual hierarchy by using size, color, contrast, and white space effectively. This helps users identify important elements on the screen quickly and understand the flow of your design easily.\n\nGrouping of Elements\n--------------------\n\nGroup related elements together, such as navigation menus or form input fields. This helps users recognize the purpose and function of each section more quickly and intuitively.\n\nBalance and Proportion\n----------------------\n\nCreate a balanced and proportional look by distributing elements on the screen evenly. This can be achieved through the use of grids or other layout techniques that help maintain a sense of harmony and order in your design.\n\nAccessibility\n-------------\n\nEnsure your design is accessible to all users by considering factors such as text size, contrast, and color combinations. Aim to create an inclusive prototype that caters to people of different abilities and preferences.\n\nResponsiveness and Flexibility\n------------------------------\n\nMake sure your prototype can adapt to different screen sizes and devices, ensuring a seamless user experience across various platforms. This is particularly important when designing for web and mobile applications.\n\nIterating and Testing\n---------------------\n\nAs you develop your design, continually test and iterate on your layout based on user feedback and data. This process will help refine your design and ensure it meets the needs and expectations of your users.\n\nBy incorporating these good layout rules into your prototyping process, you'll be well on your way to creating a user-friendly and effective design that meets the goals and objectives of your project.",
"links": []
"description": "In the world of UX design, a good layout is crucial to ensure your prototype is intuitive and user-friendly. By following these good layout rules, you can ensure your designs are efficient, attractive, and easy to navigate for users.\n\nConsistency\n-----------\n\nBeing consistent with your design is vital in creating an easy-to-navigate interface. Utilize the same color schemes, typography, and other design elements consistently throughout your prototype to make it visually cohesive and user-friendly.\n\nAlignment and Spacing\n---------------------\n\nEnsure all the elements on your prototype are aligned and spaced properly. This helps create a well-structured and clean look, while also making it easy for users to navigate and understand your design.\n\nVisual Hierarchy\n----------------\n\nEstablish clear visual hierarchy by using size, color, contrast, and white space effectively. This helps users identify important elements on the screen quickly and understand the flow of your design easily.\n\nGrouping of Elements\n--------------------\n\nGroup related elements together, such as navigation menus or form input fields. This helps users recognize the purpose and function of each section more quickly and intuitively.\n\nBalance and Proportion\n----------------------\n\nCreate a balanced and proportional look by distributing elements on the screen evenly. This can be achieved through the use of grids or other layout techniques that help maintain a sense of harmony and order in your design.\n\nAccessibility\n-------------\n\nEnsure your design is accessible to all users by considering factors such as text size, contrast, and color combinations. Aim to create an inclusive prototype that caters to people of different abilities and preferences.\n\nResponsiveness and Flexibility\n------------------------------\n\nMake sure your prototype can adapt to different screen sizes and devices, ensuring a seamless user experience across various platforms. This is particularly important when designing for web and mobile applications.\n\nIterating and Testing\n---------------------\n\nAs you develop your design, continually test and iterate on your layout based on user feedback and data. This process will help refine your design and ensure it meets the needs and expectations of your users.\n\nBy incorporating these good layout rules into your prototyping process, you'll be well on your way to creating a user-friendly and effective design that meets the goals and objectives of your project.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "User Interface Design Guidelines: 10 Rules of Thumb",
"url": "https://www.interaction-design.org/literature/article/user-interface-design-guidelines-10-rules-of-thumb",
"type": "article"
}
]
},
"t46s6Piyd8MoJYzdDTsjr": {
"title": "Figma",

View File

@@ -234,6 +234,11 @@
"title": "Global Properties",
"description": "Global properties allows you to add properties or methods that can be accessed throughout your application. This is particularly useful for sharing functionality or data across components without the need to pass props explicitly.\n\nVisit the following resources to learn more:",
"links": [
{
"title": "Application API - globalProperties",
"url": "https://vuejs.org/api/application.html#app-config-globalproperties",
"type": "article"
},
{
"title": "Vue.js Global Properties",
"url": "https://blog.logrocket.com/vue-js-globalproperties/",
@@ -544,7 +549,7 @@
"links": [
{
"title": "Modifiers",
"url": "https://v2.vuejs.org/v2/guide/components-custom-events.html",
"url": "https://vuejs.org/guide/essentials/forms.html#modifiers",
"type": "article"
}
]
@@ -568,11 +573,6 @@
"title": "Binding Events",
"url": "https://vuejs.org/guide/essentials/event-handling",
"type": "article"
},
{
"title": "Vue.js Event Handling",
"url": "https://www.geeksforgeeks.org/vue-js-event-handling/",
"type": "article"
}
]
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 512 KiB

View File

@@ -43,6 +43,7 @@ Here is the list of available roadmaps with more being actively worked upon.
- [AI and Data Scientist Roadmap](https://roadmap.sh/ai-data-scientist)
- [AI Engineer Roadmap](https://roadmap.sh/ai-engineer)
- [AWS Roadmap](https://roadmap.sh/aws)
- [Cloudflare Roadmap](https://roadmap.sh/cloudflare)
- [Linux Roadmap](https://roadmap.sh/linux)
- [Terraform Roadmap](https://roadmap.sh/terraform)
- [Data Analyst Roadmap](https://roadmap.sh/data-analyst)

View File

@@ -0,0 +1,181 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Edge, Node } from 'reactflow';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger';
import { runPromisesInBatchSequentially } from '../src/lib/promise';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { generateText } from 'ai';
// ERROR: `__dirname` is not defined in ES module scope
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Usage: tsx ./scripts/editor-roadmap-content.ts <roadmapId>
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
console.log('GEMINI_API_KEY:', GEMINI_API_KEY);
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
const roadmapId = process.argv[2];
const google = createGoogleGenerativeAI({
apiKey: process.env.GEMINI_API_KEY,
});
const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
if (!roadmapId) {
console.error('Roadmap Id is required');
process.exit(1);
}
if (!allowedRoadmapIds.includes(roadmapId)) {
console.error(`Invalid roadmap key ${roadmapId}`);
console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
process.exit(1);
}
const roadmapFrontmatterDir = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
`${roadmapId}.md`,
);
const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
const { data } = matter(roadmapFrontmatterRaw);
const roadmapFrontmatter = data as RoadmapFrontmatter;
if (!roadmapFrontmatter) {
console.error('Invalid roadmap frontmatter');
process.exit(1);
}
if (roadmapFrontmatter.renderer !== 'editor') {
console.error('Only Editor Rendered Roadmaps are allowed');
process.exit(1);
}
const roadmapDir = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
`${roadmapId}.json`,
);
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
let { nodes, edges } = JSON.parse(roadmapContent) as {
nodes: Node[];
edges: Edge[];
};
const enrichedNodes = nodes
.filter(
(node) =>
node?.type &&
['topic', 'subtopic'].includes(node.type) &&
node.data?.label,
)
.map((node) => {
// Because we only need the parent id and title for subtopics
if (node.type !== 'subtopic') {
return node;
}
const parentNodeId =
edges.find((edge) => edge.target === node.id)?.source || '';
const parentNode = nodes.find((n) => n.id === parentNodeId);
return {
...node,
parentId: parentNodeId,
parentTitle: parentNode?.data?.label || '',
};
}) as (Node & { parentId?: string; parentTitle?: string })[];
const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
const stats = await fs.stat(roadmapContentDir).catch(() => null);
if (!stats || !stats.isDirectory()) {
await fs.mkdir(roadmapContentDir, { recursive: true });
}
function writeTopicContent(
roadmapTitle: string,
childTopic: string,
parentTopic?: string,
) {
const updatedTitle = roadmapTitle.replace('Roadmap', '').trim().replace('Developer', '');
let prompt = `I will give you a topic and you need to write a brief introduction for that in "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:
# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" or "Subtopic under XYZ" etc.)
(Briefly explain the topic in one paragraph using simple english. Don't start with explaining how important the topic is with regard to "${roadmapTitle}". Don't say something along the lines of "XYZ plays a crucial role in ${roadmapTitle}". Don't include anything saying "In the context of ${roadmapTitle}". Instead, start with a simple explanation of the topic itself. For example, if the topic is "React", you can start with "React is a JavaScript library for building user interfaces."".)
`;
if (!parentTopic) {
prompt += `First topic is: ${childTopic}`;
} else {
prompt += `First topic is: "${parentTopic} > ${childTopic}"`;
}
return new Promise((resolve, reject) => {
generateText({
model: google('gemini-2.0-flash'),
prompt: prompt,
providerOptions: {
}
})
.then((response) => {
const article = response.text;
resolve(article);
})
.catch((err) => {
reject(err);
});
});
}
async function writeNodeContent(node: Node & { parentTitle?: string }) {
const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`;
if (!roadmapContentFiles.includes(nodeDirPattern)) {
console.log(`Missing file for: ${nodeDirPattern}`);
return;
}
const nodeDir = path.join(roadmapContentDir, nodeDirPattern);
const nodeContent = await fs.readFile(nodeDir, 'utf-8');
const isFileEmpty = !nodeContent.replace(`# ${node.data.label}`, '').trim();
if (!isFileEmpty) {
console.log(`❌ Ignoring ${nodeDirPattern}. Not empty.`);
return;
}
const topic = node.data.label;
const parentTopic = node.parentTitle;
console.log(`⏳ Generating content for ${topic}...`);
let newContentFile = '';
if (GEMINI_API_KEY) {
newContentFile = (await writeTopicContent(
roadmapFrontmatter.title,
topic,
parentTopic,
)) as string;
} else {
newContentFile = `# ${topic}`;
}
await fs.writeFile(nodeDir, newContentFile, 'utf-8');
console.log(`✅ Content generated for ${topic}`);
}
let roadmapContentFiles = await fs.readdir(roadmapContentDir, {
recursive: true,
});
if (!GEMINI_API_KEY) {
console.log('----------------------------------------');
console.log('GEMINI_API_KEY not found. Skipping gemini api calls...');
console.log('----------------------------------------');
}
const promises = enrichedNodes.map((node) => () => writeNodeContent(node));
await runPromisesInBatchSequentially(promises, 20);
console.log('✅ All content generated');

View File

@@ -18,3 +18,16 @@ export function aiRoadmapApi(context: APIContext) {
},
};
}
export interface AICourseDocument {
_id: string;
userId: string;
title: string;
slug?: string;
keyword: string;
difficulty: string;
data: string;
viewCount: number;
createdAt: Date;
updatedAt: Date;
}

View File

@@ -27,7 +27,7 @@ const sidebarLinks = [
href: '/account/update-profile',
title: 'Profile',
id: 'profile',
isNew: true,
isNew: false,
icon: {
glyph: 'user',
classes: 'h-4 w-4',
@@ -56,7 +56,7 @@ const sidebarLinks = [
},
{
href: '/account/road-card',
title: 'Card',
title: 'Road Card',
id: 'road-card',
isNew: false,
icon: {
@@ -64,6 +64,16 @@ const sidebarLinks = [
classes: 'h-4 w-4',
},
},
{
href: '/account/billing',
title: 'Billing',
id: 'billing',
isNew: true,
icon: {
glyph: 'credit-card',
classes: 'h-4 w-4',
},
},
{
href: '/account/settings',
title: 'Settings',
@@ -97,7 +107,7 @@ const sidebarLinks = [
}`}
>
<AstroIcon icon={'users'} class={`h-4 w-4 mr-2`} />
Teams
Teams
</a>
</li>
{

View File

@@ -1,5 +1,4 @@
import { getUser } from '../../lib/jwt';
import { getPercentage } from '../../helper/number';
import { ProjectProgressActions } from './ProjectProgressActions';
import { cn } from '../../lib/classname';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';

View File

@@ -1,7 +1,7 @@
import { getUser } from '../../lib/jwt';
import { getPercentage } from '../../helper/number';
import { ResourceProgressActions } from './ResourceProgressActions';
import { cn } from '../../lib/classname';
import { getPercentage } from '../../lib/number';
type ResourceProgressType = {
resourceType: 'roadmap' | 'best-practice';

View File

@@ -86,9 +86,6 @@ export function AdvertiseForm() {
pageProgressMessage.set('Please wait');
// Placeholder function to send data
console.log('Form data:', formData);
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-advertise`,
formData,

View File

@@ -0,0 +1,14 @@
<script type='text/javascript'>
(function (c, l, a, r, i, t, y) {
c[a] =
c[a] ||
function () {
(c[a].q = c[a].q || []).push(arguments);
};
t = l.createElement(r);
t.async = 1;
t.src = 'https://www.clarity.ms/tag/' + i;
y = l.getElementsByTagName(r)[0];
y.parentNode.insertBefore(t, y);
})(window, document, 'clarity', 'script', 'qcw723i36o');
</script>

View File

@@ -6,6 +6,7 @@ declare global {
category: string;
label?: string;
value?: string;
callback?: () => void;
}) => void;
}
}
@@ -17,7 +18,7 @@ declare global {
* @returns void
*/
window.fireEvent = (props) => {
const { action, category, label, value } = props;
const { action, category, label, value, callback } = props;
if (!window.gtag) {
console.warn('Missing GTAG - Analytics disabled');
return;
@@ -25,11 +26,16 @@ window.fireEvent = (props) => {
if (import.meta.env.DEV) {
console.log('Analytics event fired', props);
callback?.();
return;
}
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
...(callback ? { event_callback: callback } : {}),
});
};
export {};

View File

@@ -1,6 +1,5 @@
---
import { parse } from 'node-html-parser';
import type { Attributes } from 'node-html-parser/dist/nodes/html';
export interface Props {
icon: string;
@@ -15,7 +14,6 @@ async function getSVG(name: string) {
eager: true,
});
if (!(filepath in files)) {
throw new Error(`${filepath} not found`);
}

View File

@@ -0,0 +1,143 @@
import { useEffect, useState } from 'react';
import { Modal } from '../Modal';
import { GitHubButton } from './GitHubButton';
import { GoogleButton } from './GoogleButton';
import { LinkedInButton } from './LinkedInButton';
import { EmailLoginForm } from './EmailLoginForm';
import { EmailSignupForm } from './EmailSignupForm';
type CourseLoginPopupProps = {
onClose: () => void;
checkoutAfterLogin?: boolean;
};
export const CHECKOUT_AFTER_LOGIN_KEY = 'checkoutAfterLogin';
export function CourseLoginPopup(props: CourseLoginPopupProps) {
const { onClose: parentOnClose, checkoutAfterLogin = true } = props;
const [isDisabled, setIsDisabled] = useState(false);
const [isUsingEmail, setIsUsingEmail] = useState(false);
const [emailNature, setEmailNature] = useState<'login' | 'signup' | null>(
null,
);
function onClose() {
// if user didn't login and closed the popup, we remove the checkoutAfterLogin flag
// so that login from other buttons on course page will trigger purchase
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
parentOnClose();
}
useEffect(() => {
localStorage.setItem(
CHECKOUT_AFTER_LOGIN_KEY,
checkoutAfterLogin ? '1' : '0',
);
}, [checkoutAfterLogin]);
if (emailNature) {
const emailHeader = (
<div className="mb-7 text-center">
<p className="mb-3.5 pt-2 text-2xl font-semibold leading-5 text-slate-900">
{emailNature === 'login'
? 'Login to your account'
: 'Create an account'}
</p>
<p className="mt-2 text-sm leading-4 text-slate-600">
Fill in the details below to continue
</p>
</div>
);
return (
<Modal onClose={onClose} bodyClassName="p-5 h-auto">
{emailHeader}
{emailNature === 'login' && (
<EmailLoginForm
isDisabled={isDisabled}
setIsDisabled={setIsDisabled}
/>
)}
{emailNature === 'signup' && (
<EmailSignupForm
isDisabled={isDisabled}
setIsDisabled={setIsDisabled}
/>
)}
<button
className="mt-2 w-full rounded-md border border-gray-400 py-2 text-center text-sm text-gray-600 hover:bg-gray-100"
onClick={() => setEmailNature(null)}
>
Back to Options
</button>
</Modal>
);
}
return (
<Modal onClose={onClose} bodyClassName="p-5 h-auto">
<div className="mb-7 text-center">
<p className="mb-3.5 pt-2 text-2xl font-semibold leading-5 text-slate-900">
Create or login to your account
</p>
<p className="mt-2 text-sm leading-4 text-slate-600">
Login or sign up for an account to start learning
</p>
</div>
<div className="flex w-full flex-col gap-2">
<GitHubButton
className="rounded-md border-gray-400 hover:bg-gray-100"
isDisabled={isDisabled}
setIsDisabled={setIsDisabled}
/>
<GoogleButton
className="rounded-md border-gray-400 hover:bg-gray-100"
isDisabled={isDisabled}
setIsDisabled={setIsDisabled}
/>
<LinkedInButton
className="rounded-md border-gray-400 hover:bg-gray-100"
isDisabled={isDisabled}
setIsDisabled={setIsDisabled}
/>
</div>
<div className="flex w-full items-center gap-4 py-6 text-sm text-gray-600">
<div className="h-px w-full bg-gray-200" />
OR
<div className="h-px w-full bg-gray-200" />
</div>
<div className="flex flex-row gap-2">
{!isUsingEmail && (
<button
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
onClick={() => setIsUsingEmail(true)}
>
Use your email address
</button>
)}
{isUsingEmail && (
<>
<button
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
onClick={() => setEmailNature('login')}
>
Already have an account
</button>
<button
className="flex-grow rounded-md border border-gray-400 px-4 py-2 text-sm text-gray-600 hover:bg-gray-100"
onClick={() => setEmailNature('signup')}
>
Create an account
</button>
</>
)}
</div>
</Modal>
);
}

View File

@@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
import type { FormEvent } from 'react';
import { useId, useState } from 'react';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { FIRST_LOGIN_PARAM, setAuthToken } from '../../lib/jwt';
type EmailLoginFormProps = {
isDisabled?: boolean;
@@ -24,19 +24,22 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
setIsDisabled?.(true);
setError('');
const { response, error } = await httpPost<{ token: string }>(
`${import.meta.env.PUBLIC_API_URL}/v1-login`,
{
email,
password,
},
);
const { response, error } = await httpPost<{
token: string;
isNewUser: boolean;
}>(`${import.meta.env.PUBLIC_API_URL}/v1-login`, {
email,
password,
});
// Log the user in and reload the page
if (response?.token) {
setAuthToken(response.token);
window.location.reload();
const currentLocation = window.location.href;
const url = new URL(currentLocation, window.location.origin);
url.searchParams.set(FIRST_LOGIN_PARAM, response?.isNewUser ? '1' : '0');
window.location.href = url.toString();
return;
}

View File

@@ -1,21 +1,27 @@
import { useEffect, useState } from 'react';
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import {
FIRST_LOGIN_PARAM,
COURSE_PURCHASE_PARAM,
setAuthToken,
} from '../../lib/jwt';
import { cn } from '../../lib/classname.ts';
import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { triggerUtmRegistration } from '../../lib/browser.ts';
type GitHubButtonProps = {
isDisabled?: boolean;
setIsDisabled?: (isDisabled: boolean) => void;
className?: string;
};
const GITHUB_REDIRECT_AT = 'githubRedirectAt';
const GITHUB_LAST_PAGE = 'githubLastPage';
export function GitHubButton(props: GitHubButtonProps) {
const { isDisabled, setIsDisabled } = props;
const { isDisabled, setIsDisabled, className } = props;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
@@ -32,7 +38,7 @@ export function GitHubButton(props: GitHubButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
httpGet<{ token: string }>(
httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-github-callback${
window.location.search
}`,
@@ -49,7 +55,7 @@ export function GitHubButton(props: GitHubButtonProps) {
triggerUtmRegistration();
let redirectUrl = '/';
let redirectUrl = new URL('/', window.location.origin);
const gitHubRedirectAt = localStorage.getItem(GITHUB_REDIRECT_AT);
const lastPageBeforeGithub = localStorage.getItem(GITHUB_LAST_PAGE);
@@ -61,20 +67,37 @@ export function GitHubButton(props: GitHubButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
redirectUrl = lastPageBeforeGithub;
redirectUrl = new URL(lastPageBeforeGithub, window.location.origin);
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
redirectUrl = authRedirectUrl;
redirectUrl = new URL(authRedirectUrl, window.location.origin);
}
localStorage.removeItem(GITHUB_REDIRECT_AT);
localStorage.removeItem(GITHUB_LAST_PAGE);
setAuthToken(response.token);
window.location.href = redirectUrl;
redirectUrl.searchParams.set(
FIRST_LOGIN_PARAM,
response?.isNewUser ? '1' : '0',
);
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
if (
redirectUrl.pathname.includes('/courses/sql') &&
shouldTriggerPurchase
) {
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
@@ -120,7 +143,10 @@ export function GitHubButton(props: GitHubButtonProps) {
return (
<>
<button
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className={cn(
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
className,
)}
disabled={isLoading || isDisabled}
onClick={handleClick}
>

View File

@@ -1,24 +1,32 @@
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import {
FIRST_LOGIN_PARAM,
TOKEN_COOKIE_NAME,
setAuthToken,
} from '../../lib/jwt';
import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { COURSE_PURCHASE_PARAM } from '../../lib/jwt';
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import {
getStoredUtmParams,
triggerUtmRegistration,
} from '../../lib/browser.ts';
import { cn } from '../../lib/classname.ts';
type GoogleButtonProps = {
isDisabled?: boolean;
setIsDisabled?: (isDisabled: boolean) => void;
className?: string;
};
const GOOGLE_REDIRECT_AT = 'googleRedirectAt';
const GOOGLE_LAST_PAGE = 'googleLastPage';
export function GoogleButton(props: GoogleButtonProps) {
const { isDisabled, setIsDisabled } = props;
const { isDisabled, setIsDisabled, className } = props;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
@@ -35,14 +43,12 @@ export function GoogleButton(props: GoogleButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
httpGet<{ token: string }>(
httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-google-callback${
window.location.search
}`,
)
.then(({ response, error }) => {
const utmParams = getStoredUtmParams();
if (!response?.token) {
setError(error?.message || 'Something went wrong.');
setIsLoading(false);
@@ -53,7 +59,7 @@ export function GoogleButton(props: GoogleButtonProps) {
triggerUtmRegistration();
let redirectUrl = '/';
let redirectUrl = new URL('/', window.location.origin);
const googleRedirectAt = localStorage.getItem(GOOGLE_REDIRECT_AT);
const lastPageBeforeGoogle = localStorage.getItem(GOOGLE_LAST_PAGE);
@@ -65,20 +71,37 @@ export function GoogleButton(props: GoogleButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
redirectUrl = lastPageBeforeGoogle;
redirectUrl = new URL(lastPageBeforeGoogle, window.location.origin);
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
redirectUrl = authRedirectUrl;
redirectUrl = new URL(authRedirectUrl, window.location.origin);
}
redirectUrl.searchParams.set(
FIRST_LOGIN_PARAM,
response?.isNewUser ? '1' : '0',
);
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
if (
redirectUrl.pathname.includes('/courses/sql') &&
shouldTriggerPurchase
) {
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
localStorage.removeItem(GOOGLE_REDIRECT_AT);
localStorage.removeItem(GOOGLE_LAST_PAGE);
setAuthToken(response.token);
window.location.href = redirectUrl;
window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
@@ -130,7 +153,10 @@ export function GoogleButton(props: GoogleButtonProps) {
return (
<>
<button
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className={cn(
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 hover:bg-gray-50 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
className,
)}
disabled={isLoading || isDisabled}
onClick={handleClick}
>

View File

@@ -1,21 +1,29 @@
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import {
FIRST_LOGIN_PARAM,
COURSE_PURCHASE_PARAM,
TOKEN_COOKIE_NAME,
setAuthToken,
} from '../../lib/jwt';
import { cn } from '../../lib/classname.ts';
import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { triggerUtmRegistration } from '../../lib/browser.ts';
type LinkedInButtonProps = {
isDisabled?: boolean;
setIsDisabled?: (isDisabled: boolean) => void;
className?: string;
};
const LINKEDIN_REDIRECT_AT = 'linkedInRedirectAt';
const LINKEDIN_LAST_PAGE = 'linkedInLastPage';
export function LinkedInButton(props: LinkedInButtonProps) {
const { isDisabled, setIsDisabled } = props;
const { isDisabled, setIsDisabled, className } = props;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
@@ -32,7 +40,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
httpGet<{ token: string }>(
httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
window.location.search
}`,
@@ -48,7 +56,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
triggerUtmRegistration();
let redirectUrl = '/';
let redirectUrl = new URL('/', window.location.origin);
const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT);
const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
@@ -60,20 +68,39 @@ export function LinkedInButton(props: LinkedInButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
redirectUrl = lastPageBeforeLinkedIn;
redirectUrl = new URL(
lastPageBeforeLinkedIn,
window.location.origin,
);
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
redirectUrl = authRedirectUrl;
redirectUrl = new URL(authRedirectUrl, window.location.origin);
}
redirectUrl.searchParams.set(
FIRST_LOGIN_PARAM,
response?.isNewUser ? '1' : '0',
);
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
if (
redirectUrl.pathname.includes('/courses/sql') &&
shouldTriggerPurchase
) {
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
localStorage.removeItem(LINKEDIN_REDIRECT_AT);
localStorage.removeItem(LINKEDIN_LAST_PAGE);
setAuthToken(response.token);
window.location.href = redirectUrl;
window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
@@ -125,14 +152,17 @@ export function LinkedInButton(props: LinkedInButtonProps) {
return (
<>
<button
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60"
className={cn(
'inline-flex h-10 w-full items-center justify-center gap-2 rounded border border-slate-300 bg-white p-2 text-sm font-medium text-black outline-none hover:border-gray-400 focus:ring-2 focus:ring-[#333] focus:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-60',
className,
)}
disabled={isLoading || isDisabled}
onClick={handleClick}
>
{isLoading ? (
<Spinner className={'h-[18px] w-[18px]'} isDualRing={false} />
) : (
<LinkedInIcon className={'h-[18px] w-[18px]'} />
<LinkedInIcon className={'h-[18px] w-[18px] text-blue-700'} />
)}
Continue with LinkedIn
</button>

View File

@@ -7,7 +7,7 @@ import { AuthenticationForm } from './AuthenticationForm';
<Popup id='login-popup' title='' subtitle=''>
<div class='mb-7 text-center'>
<p class='mb-3 text-2xl font-semibold leading-5 text-slate-900'>
Login to your account
Login or Signup
</p>
<p class='mt-2 text-sm leading-4 text-slate-600'>
You must be logged in to perform this action.

View File

@@ -1,7 +1,11 @@
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { httpPost } from '../../lib/http';
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import {
FIRST_LOGIN_PARAM,
TOKEN_COOKIE_NAME,
setAuthToken,
} from '../../lib/jwt';
import { Spinner } from '../ReactIcons/Spinner';
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
import { triggerUtmRegistration } from '../../lib/browser.ts';
@@ -13,7 +17,7 @@ export function TriggerVerifyAccount() {
const triggerVerify = (code: string) => {
setIsLoading(true);
httpPost<{ token: string }>(
httpPost<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-verify-account`,
{
code,
@@ -30,7 +34,13 @@ export function TriggerVerifyAccount() {
triggerUtmRegistration();
setAuthToken(response.token);
window.location.href = '/';
const url = new URL('/', window.location.origin);
url.searchParams.set(
FIRST_LOGIN_PARAM,
response?.isNewUser ? '1' : '0',
);
window.location.href = url.toString();
})
.catch((err) => {
setIsLoading(false);

View File

@@ -0,0 +1,226 @@
import { useEffect, useState } from 'react';
import { pageProgressMessage } from '../../stores/page';
import { useToast } from '../../hooks/use-toast';
import { useMutation, useQuery } from '@tanstack/react-query';
import {
billingDetailsOptions,
USER_SUBSCRIPTION_PLAN_PRICES,
} from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
import { httpPost } from '../../lib/query-http';
import { UpgradeAccountModal } from './UpgradeAccountModal';
import { getUrlParams } from '../../lib/browser';
import { VerifyUpgrade } from './VerifyUpgrade';
import { EmptyBillingScreen } from './EmptyBillingScreen';
import {
Calendar,
RefreshCw,
Loader2,
CreditCard,
ArrowRightLeft,
CircleX,
} from 'lucide-react';
import { BillingWarning } from './BillingWarning';
export type CreateCustomerPortalBody = {};
export type CreateCustomerPortalResponse = {
url: string;
};
export function BillingPage() {
const toast = useToast();
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showVerifyUpgradeModal, setShowVerifyUpgradeModal] = useState(false);
const { data: billingDetails, isPending: isLoadingBillingDetails } = useQuery(
billingDetailsOptions(),
queryClient,
);
const isCanceled =
billingDetails?.status === 'canceled' || billingDetails?.cancelAtPeriodEnd;
const isPastDue = billingDetails?.status === 'past_due';
const {
mutate: createCustomerPortal,
isSuccess: isCreatingCustomerPortalSuccess,
isPending: isCreatingCustomerPortal,
} = useMutation(
{
mutationFn: (body: CreateCustomerPortalBody) => {
return httpPost<CreateCustomerPortalResponse>(
'/v1-create-customer-portal',
body,
);
},
onSuccess: (data) => {
window.location.href = data.url;
},
onError: (error) => {
console.error(error);
toast.error(error?.message || 'Failed to Create Customer Portal');
},
},
queryClient,
);
useEffect(() => {
if (isLoadingBillingDetails) {
return;
}
pageProgressMessage.set('');
const shouldVerifyUpgrade = getUrlParams()?.s === '1';
if (shouldVerifyUpgrade) {
setShowVerifyUpgradeModal(true);
}
}, [isLoadingBillingDetails]);
if (isLoadingBillingDetails || !billingDetails) {
return null;
}
const selectedPlanDetails = USER_SUBSCRIPTION_PLAN_PRICES.find(
(plan) => plan.priceId === billingDetails?.priceId,
);
const priceDetails = selectedPlanDetails;
const formattedNextBillDate = new Date(
billingDetails?.currentPeriodEnd || '',
).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return (
<>
{showUpgradeModal && (
<UpgradeAccountModal
onClose={() => {
setShowUpgradeModal(false);
}}
success="/account/billing?s=1"
cancel="/account/billing"
/>
)}
{showVerifyUpgradeModal && <VerifyUpgrade />}
{billingDetails?.status === 'none' && !isLoadingBillingDetails && (
<EmptyBillingScreen onUpgrade={() => setShowUpgradeModal(true)} />
)}
{billingDetails?.status !== 'none' &&
!isLoadingBillingDetails &&
priceDetails && (
<div className="mt-1">
{isCanceled && (
<BillingWarning
icon={CircleX}
message="Your subscription has been canceled."
buttonText="Reactivate?"
onButtonClick={() => {
createCustomerPortal({});
}}
isLoading={
isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
}
/>
)}
{isPastDue && (
<BillingWarning
message="We were not able to charge your card."
buttonText="Update payment information."
onButtonClick={() => {
createCustomerPortal({});
}}
isLoading={
isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
}
/>
)}
<h2 className="mb-2 text-xl font-semibold text-black">
Current Subscription
</h2>
<p className="text-sm text-gray-500">
Thank you for being a pro member. Your plan details are below.
</p>
<div className="mt-8 flex flex-col gap-6 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gray-100">
<RefreshCw className="size-5 text-gray-600" />
</div>
<div>
<span className="text-xs uppercase tracking-wider text-gray-400">
Payment
</span>
<h3 className="flex items-baseline text-lg font-semibold text-black">
${priceDetails.amount}
<span className="ml-1 text-sm font-normal text-gray-500">
/ {priceDetails.interval}
</span>
</h3>
</div>
</div>
</div>
<div className="mt-6 border-t border-gray-100 pt-6">
<div className="flex items-start gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-gray-100">
<Calendar className="size-5 text-gray-600" />
</div>
<div>
<span className="text-xs uppercase tracking-wider text-gray-400">
{billingDetails?.cancelAtPeriodEnd
? 'Expires On'
: 'Renews On'}
</span>
<h3 className="text-lg font-semibold text-black">
{formattedNextBillDate}
</h3>
</div>
</div>
<div className="mt-8 flex gap-3 max-sm:flex-col">
{!isCanceled && (
<button
className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 max-sm:flex-grow"
onClick={() => {
setShowUpgradeModal(true);
}}
>
<ArrowRightLeft className="mr-2 h-4 w-4" />
Switch Plan
</button>
)}
<button
className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
onClick={() => {
createCustomerPortal({});
}}
disabled={
isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
}
>
{isCreatingCustomerPortal ||
isCreatingCustomerPortalSuccess ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<CreditCard className="mr-2 h-4 w-4" />
)}
Manage Subscription
</button>
</div>
</div>
</div>
)}
</>
);
}

View File

@@ -0,0 +1,39 @@
import { AlertTriangle, type LucideIcon } from 'lucide-react';
export type BillingWarningProps = {
icon?: LucideIcon;
message: string;
onButtonClick?: () => void;
buttonText?: string;
isLoading?: boolean;
};
export function BillingWarning(props: BillingWarningProps) {
const {
message,
onButtonClick,
buttonText,
isLoading,
icon: Icon = AlertTriangle,
} = props;
return (
<div className="mb-6 flex items-center gap-2 rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-600">
<Icon className="h-5 w-5" />
<span>
{message}
{buttonText && (
<button
disabled={isLoading}
onClick={() => {
onButtonClick?.();
}}
className="font-semibold underline underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50 ml-0.5"
>
{buttonText}
</button>
)}
</span>
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';
import { getUrlParams } from '../../lib/browser';
import { VerifyUpgrade } from "./VerifyUpgrade";
export function CheckSubscriptionVerification() {
const [shouldVerifyUpgrade, setShouldVerifyUpgrade] = useState(false);
useEffect(() => {
const params = getUrlParams();
if (params.s !== '1') {
return;
}
setShouldVerifyUpgrade(true);
}, []);
if (!shouldVerifyUpgrade) {
return null;
}
return <VerifyUpgrade />;
}

View File

@@ -0,0 +1,68 @@
import {
CreditCard,
Ellipsis,
HeartHandshake,
MessageCircleIcon,
SparklesIcon,
Zap,
} from 'lucide-react';
type EmptyBillingScreenProps = {
onUpgrade: () => void;
};
const perks = [
{
icon: Zap,
text: 'Unlimited AI course generations',
},
{
icon: MessageCircleIcon,
text: 'Unlimited AI Chat feature usage',
},
{
icon: SparklesIcon,
text: 'Early access to new features',
},
{
icon: HeartHandshake,
text: 'Support the development of platform',
},
{
icon: Ellipsis,
text: 'more perks coming soon!',
},
];
export function EmptyBillingScreen(props: EmptyBillingScreenProps) {
const { onUpgrade } = props;
return (
<div className="mt-12 flex h-full w-full flex-col items-center">
<CreditCard className="mb-3 h-12 w-12 text-gray-300" />
<h3 className="mb-3 text-xl font-semibold text-black">
No Active Subscription
</h3>
<p className="text-balance text-gray-700">
Unlock pro benefits by upgrading to a subscription
</p>
<div className="my-8 flex flex-col gap-2">
{perks.map((perk) => (
<p className="textsm flex items-center text-gray-600" key={perk.text}>
<perk.icon className="mr-2 h-4 w-4 text-gray-500" />
{perk.text}
</p>
))}
</div>
<button
onClick={onUpgrade}
className="inline-flex items-center justify-center rounded-lg bg-black px-6 py-2.5 text-sm font-medium text-white transition-colors hover:opacity-80 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
Upgrade Account
</button>
</div>
);
}

View File

@@ -0,0 +1,96 @@
import { useMutation } from '@tanstack/react-query';
import type { USER_SUBSCRIPTION_PLAN_PRICES } from '../../queries/billing';
import { Modal } from '../Modal';
import { queryClient } from '../../stores/query-client';
import { useToast } from '../../hooks/use-toast';
import { VerifyUpgrade } from './VerifyUpgrade';
import { Loader2Icon } from 'lucide-react';
import { httpPost } from '../../lib/query-http';
type UpdatePlanBody = {
priceId: string;
};
type UpdatePlanResponse = {
status: 'ok';
};
type UpdatePlanConfirmationProps = {
planDetails: (typeof USER_SUBSCRIPTION_PLAN_PRICES)[number];
onClose: () => void;
onCancel: () => void;
};
export function UpdatePlanConfirmation(props: UpdatePlanConfirmationProps) {
const { planDetails, onClose, onCancel } = props;
const toast = useToast();
const {
mutate: updatePlan,
isPending,
status,
} = useMutation(
{
mutationFn: (body: UpdatePlanBody) => {
return httpPost<UpdatePlanResponse>(
'/v1-update-subscription-plan',
body,
);
},
onError: (error) => {
console.error(error);
toast.error(error?.message || 'Failed to Create Customer Portal');
},
},
queryClient,
);
if (!planDetails) {
return null;
}
const selectedPrice = planDetails;
if (status === 'success') {
return <VerifyUpgrade newPriceId={selectedPrice.priceId} />;
}
return (
<Modal
onClose={isPending ? () => {} : onClose}
bodyClassName="rounded-xl bg-white p-6"
>
<h3 className="text-xl font-bold text-black">Subscription Update</h3>
<p className="mt-2 text-balance text-gray-600">
Your plan will be updated to the{' '}
<b className="text-black">{planDetails.interval}</b> plan, and will
be charged{' '}
<b className="text-black">
${selectedPrice.amount}/{selectedPrice.interval}
</b>
.
</p>
<div className="mt-6 grid grid-cols-2 gap-3">
<button
className="rounded-md border border-gray-200 py-2 text-sm font-semibold hover:bg-gray-50 transition-colors disabled:opacity-50"
onClick={onCancel}
disabled={isPending}
>
Cancel
</button>
<button
className="flex items-center justify-center rounded-md bg-purple-600 py-2 text-sm font-semibold text-white hover:bg-purple-500 transition-colors disabled:opacity-50"
disabled={isPending}
onClick={() => {
updatePlan({ priceId: selectedPrice.priceId });
}}
>
{isPending && (
<Loader2Icon className="size-4 animate-spin stroke-[2.5] mr-2" />
)}
{!isPending && 'Confirm'}
</button>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,306 @@
import {
Loader2,
Zap,
Infinity,
MessageSquare,
Sparkles,
Heart,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { getUser } from '../../lib/jwt';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Modal } from '../Modal';
import {
billingDetailsOptions,
USER_SUBSCRIPTION_PLAN_PRICES,
type AllowedSubscriptionInterval,
} from '../../queries/billing';
import { cn } from '../../lib/classname';
import { queryClient } from '../../stores/query-client';
import { httpPost } from '../../lib/query-http';
import { useToast } from '../../hooks/use-toast';
import { UpdatePlanConfirmation } from './UpdatePlanConfirmation';
// Define the perk type
type Perk = {
icon: LucideIcon;
title: string;
description: string;
};
// Define the perks array
const PREMIUM_PERKS: Perk[] = [
{
icon: Zap,
title: 'Unlimited AI Course Generations',
description: 'Generate as many custom courses as you need',
},
{
icon: Infinity,
title: 'No Daily Limits on course features',
description: 'Use all features without restrictions',
},
{
icon: MessageSquare,
title: 'Unlimited Course Follow-ups',
description: 'Ask as many questions as you need',
},
{
icon: Sparkles,
title: 'Early Access to Features',
description: 'Be the first to try new tools and features',
},
{
icon: Heart,
title: 'Support Development',
description: 'Help us continue building roadmap.sh',
},
];
type CreateSubscriptionCheckoutSessionBody = {
priceId: string;
success?: string;
cancel?: string;
};
type CreateSubscriptionCheckoutSessionResponse = {
checkoutUrl: string;
};
type UpgradeAccountModalProps = {
onClose: () => void;
success?: string;
cancel?: string;
};
export function UpgradeAccountModal(props: UpgradeAccountModalProps) {
const { onClose, success, cancel } = props;
const [selectedPlan, setSelectedPlan] =
useState<AllowedSubscriptionInterval>('month');
const [isUpdatingPlan, setIsUpdatingPlan] = useState(false);
const user = getUser();
const {
data: userBillingDetails,
isLoading,
error: billingError,
} = useQuery(billingDetailsOptions(), queryClient);
const toast = useToast();
const {
mutate: createCheckoutSession,
isPending: isCreatingCheckoutSession,
} = useMutation(
{
mutationFn: (body: CreateSubscriptionCheckoutSessionBody) => {
return httpPost<CreateSubscriptionCheckoutSessionResponse>(
'/v1-create-subscription-checkout-session',
body,
);
},
onSuccess: (data) => {
window.location.href = data.checkoutUrl;
},
onError: (error) => {
console.error(error);
toast.error(error?.message || 'Failed to create checkout session');
},
},
queryClient,
);
const selectedPlanDetails = USER_SUBSCRIPTION_PLAN_PRICES.find(
(plan) => plan.interval === selectedPlan,
);
const currentPlanPriceId = userBillingDetails?.priceId;
const currentPlan = USER_SUBSCRIPTION_PLAN_PRICES.find(
(plan) => plan.priceId === currentPlanPriceId,
);
useEffect(() => {
if (!currentPlan) {
return;
}
setSelectedPlan(currentPlan.interval);
}, [currentPlan]);
if (!user) {
return null;
}
const loader = isLoading ? (
<div className="absolute inset-0 flex h-[540px] w-full items-center justify-center bg-white">
<Loader2 className="h-6 w-6 animate-spin stroke-[3px] text-green-600" />
</div>
) : null;
const error = billingError;
const errorContent = error ? (
<div className="flex h-full w-full flex-col">
<p className="text-center text-red-400">
{error?.message ||
'An error occurred while loading the billing details.'}
</p>
</div>
) : null;
const calculateYearlyPrice = (monthlyPrice: number) => {
return (monthlyPrice * 12).toFixed(2);
};
if (isUpdatingPlan && selectedPlanDetails) {
return (
<UpdatePlanConfirmation
planDetails={selectedPlanDetails}
onClose={() => setIsUpdatingPlan(false)}
onCancel={() => setIsUpdatingPlan(false)}
/>
);
}
return (
<Modal
onClose={onClose}
bodyClassName="p-4 sm:p-6 bg-white"
wrapperClassName="h-auto rounded-xl max-w-3xl w-full min-h-[540px] mx-2 sm:mx-4"
overlayClassName="items-start md:items-center"
>
<div onClick={(e) => e.stopPropagation()}>
{errorContent}
{loader}
{!isLoading && !error && (
<div className="flex flex-col">
<div className="mb-6 text-left sm:mb-8">
<h2 className="text-xl font-bold text-black sm:text-2xl">
Unlock Premium Features
</h2>
<p className="mt-1 text-sm text-gray-600 sm:mt-2 sm:text-base">
Supercharge your learning experience with premium benefits
</p>
</div>
<div className="mb-6 grid grid-cols-1 gap-4 sm:mb-8 sm:gap-6 md:grid-cols-2">
{USER_SUBSCRIPTION_PLAN_PRICES.map((plan) => {
const isCurrentPlanSelected =
currentPlan?.priceId === plan.priceId;
const isYearly = plan.interval === 'year';
return (
<div
key={plan.interval}
className={cn(
'flex flex-col space-y-3 rounded-lg bg-white p-4 sm:space-y-4 sm:p-6',
isYearly
? 'border-2 border-yellow-400'
: 'border border-gray-200',
)}
>
<div className="flex items-start justify-between">
<div>
<h4 className="text-sm font-semibold text-black sm:text-base">
{isYearly ? 'Yearly Payment' : 'Monthly Payment'}
</h4>
{isYearly && (
<span className="text-xs font-medium text-green-500 sm:text-sm">
(2 months free)
</span>
)}
</div>
{isYearly && (
<span className="rounded-full bg-yellow-400 px-1.5 py-0.5 text-xs font-semibold text-black sm:px-2 sm:py-1">
Most Popular
</span>
)}
</div>
<div className="flex items-baseline">
{isYearly && (
<p className="mr-2 text-xs text-gray-400 line-through sm:text-sm">
$
{calculateYearlyPrice(
USER_SUBSCRIPTION_PLAN_PRICES[0].amount,
)}
</p>
)}
<p className="text-2xl font-bold text-black sm:text-3xl">
${plan.amount}{' '}
<span className="text-xs font-normal text-gray-500 sm:text-sm">
/ {isYearly ? 'year' : 'month'}
</span>
</p>
</div>
<div className="flex-grow"></div>
<div>
<button
className={cn(
'flex min-h-9 w-full items-center justify-center rounded-md py-2 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-yellow-400 disabled:cursor-not-allowed disabled:opacity-60 sm:min-h-11 sm:py-2.5 sm:text-base',
'bg-yellow-400 text-black hover:bg-yellow-500',
)}
disabled={
isCurrentPlanSelected || isCreatingCheckoutSession
}
onClick={() => {
setSelectedPlan(plan.interval);
if (!currentPlanPriceId) {
const currentUrlPath = window.location.pathname;
createCheckoutSession({
priceId: plan.priceId,
success: success || `${currentUrlPath}?s=1`,
cancel: cancel || `${currentUrlPath}?s=0`,
});
return;
}
setIsUpdatingPlan(true);
}}
data-1p-ignore=""
data-form-type="other"
data-lpignore="true"
>
{isCreatingCheckoutSession &&
selectedPlan === plan.interval ? (
<Loader2 className="h-3.5 w-3.5 animate-spin sm:h-4 sm:w-4" />
) : isCurrentPlanSelected ? (
'Current Plan'
) : (
'Select Plan'
)}
</button>
</div>
</div>
);
})}
</div>
{/* Benefits Section */}
<div className="grid grid-cols-1 gap-3 sm:gap-4 md:grid-cols-2">
{PREMIUM_PERKS.map((perk, index) => {
const Icon = perk.icon;
return (
<div key={index} className="flex items-start space-x-2 sm:space-x-3">
<Icon className="mt-0.5 h-4 w-4 text-yellow-500 sm:h-5 sm:w-5" />
<div>
<h4 className="text-sm font-medium text-black sm:text-base">
{perk.title}
</h4>
<p className="text-xs text-gray-600 sm:text-sm">
{perk.description}
</p>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
</Modal>
);
}

View File

@@ -0,0 +1,76 @@
import { useEffect } from 'react';
import { Loader2, CheckCircle } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
import { billingDetailsOptions } from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
import { Modal } from '../Modal';
import { deleteUrlParam } from '../../lib/browser';
type VerifyUpgradeProps = {
newPriceId?: string;
};
export function VerifyUpgrade(props: VerifyUpgradeProps) {
const { newPriceId } = props;
const { data: userBillingDetails } = useQuery(
{
...billingDetailsOptions(),
refetchInterval: 1000,
},
queryClient,
);
useEffect(() => {
if (!userBillingDetails) {
return;
}
if (
userBillingDetails.status === 'active' &&
(newPriceId ? userBillingDetails.priceId === newPriceId : true)
) {
deleteUrlParam('s');
window.location.reload();
}
}, [userBillingDetails]);
return (
<Modal
// it's an unique modal, so we don't need to close it
// user can close it by refreshing the page
onClose={() => {}}
bodyClassName="rounded-xl bg-white p-6"
>
<div className="mb-4 flex flex-col items-center text-center">
<CheckCircle className="mb-3 h-12 w-12 text-green-600" />
<h3 className="text-xl font-bold text-black">Subscription Activated</h3>
</div>
<p className="mt-2 text-balance text-center text-gray-600">
Your subscription has been activated successfully.
</p>
<p className="mt-4 text-balance text-center text-gray-600">
It might take a minute for the changes to reflect. We will{' '}
<b className="text-black">reload</b> the page for you.
</p>
<div className="my-6 flex animate-pulse items-center justify-center gap-2">
<Loader2 className="size-4 animate-spin stroke-[2.5px] text-green-600" />
<span className="text-gray-600">Please wait...</span>
</div>
<p className="text-center text-sm text-gray-500">
If it takes longer than expected, please email us at{' '}
<a
href="mailto:info@roadmap.sh"
className="text-blue-600 underline underline-offset-2 hover:text-blue-700"
>
info@roadmap.sh
</a>
.
</p>
</Modal>
);
}

View File

@@ -1,7 +1,7 @@
---
import { getAllChangelogs } from '../lib/changelog';
import { DateTime } from 'luxon';
import AstroIcon from './AstroIcon.astro';
const allChangelogs = await getAllChangelogs();
const top10Changelogs = allChangelogs.slice(0, 10);
---
@@ -12,16 +12,18 @@ const top10Changelogs = allChangelogs.slice(0, 10);
<img
src='/images/gifs/rocket.gif'
alt='Rocket'
class='mr-2 hidden sm:inline h-12 w-12'
class='mr-2 hidden h-12 w-12 sm:inline'
/>
Actively Maintained
</p>
<p class='mt-1 mb-2 sm:my-2 text-sm leading-relaxed text-gray-600 sm:my-5 sm:text-lg'>
<p
class='mb-2 mt-1 text-sm leading-relaxed text-gray-600 sm:my-2 sm:my-5 sm:text-lg'
>
We are always improving our content, adding new resources and adding
features to enhance your learning experience.
</p>
<div class='relative mt-2 sm:mt-8 text-left'>
<div class='relative mt-2 text-left sm:mt-8'>
<div
class='absolute inset-y-0 left-[120px] hidden w-px -translate-x-[0.5px] translate-x-[5.75px] bg-gray-300 sm:block'
>
@@ -36,13 +38,13 @@ const top10Changelogs = allChangelogs.slice(0, 10);
<li class='relative'>
<a
href={`/changelog#${changelog.id}`}
class='flex flex-col sm:flex-row items-start sm:items-center'
class='flex flex-col items-start sm:flex-row sm:items-center'
>
<span class='sm:w-[120px] flex-shrink-0 pr-0 sm:pr-4 text-right text-sm tracking-wide text-gray-400'>
<span class='flex-shrink-0 pr-0 text-right text-sm tracking-wide text-gray-400 sm:w-[120px] sm:pr-4'>
{formattedDate}
</span>
<span class='h-3 w-3 flex-shrink-0 rounded-full bg-gray-300 hidden sm:block' />
<span class='text-balance sm:pl-8 text-base font-medium text-gray-900'>
<span class='hidden h-3 w-3 flex-shrink-0 rounded-full bg-gray-300 sm:block' />
<span class='text-balance text-base font-medium text-gray-900 sm:pl-8'>
{changelog.frontmatter.title}
</span>
</a>
@@ -52,13 +54,23 @@ const top10Changelogs = allChangelogs.slice(0, 10);
}
</ul>
</div>
<div class='mt-2 sm:mt-8 text-left sm:text-center'>
<div
class='mt-2 flex flex-col gap-2 sm:flex-row sm:mt-8 sm:items-center sm:justify-center'
>
<a
href='/changelog'
class='inline-block text-sm sm:text-base rounded-lg sm:rounded-full bg-gray-800 px-4 sm:px-6 py-2 text-white transition-colors hover:bg-gray-700'
class='inline-block rounded-lg border border-black bg-black px-4 py-2 text-sm text-white transition-colors hover:bg-gray-700 sm:rounded-full sm:px-6 sm:text-base'
>
View Full Changelog
</a>
<button
data-guest-required
data-popup='login-popup'
class='flex flex-row items-center gap-2 rounded-lg border border-black bg-white px-4 py-2 text-sm text-black transition-all hover:bg-black hover:text-white sm:rounded-full sm:pl-4 sm:pr-5 sm:text-base'
>
<AstroIcon icon='bell' class='h-5 w-5' />
Subscribe for Notifications
</button>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { getPercentage } from '../../helper/number';
import { getRelativeTimeString } from '../../lib/date';
import { getPercentage } from '../../lib/number';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
type DashboardCustomProgressCardProps = {

View File

@@ -1,20 +1,27 @@
import { useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { useStore } from '@nanostores/react';
import { useEffect, useState } from 'react';
import { cn } from '../../../editor/utils/classname';
import { useParams } from '../../hooks/use-params';
import { useToast } from '../../hooks/use-toast';
import { httpGet } from '../../lib/http';
import { getUser } from '../../lib/jwt';
import { $teamList } from '../../stores/team';
import type { TeamListResponse } from '../TeamDropdown/TeamDropdown';
import { DashboardTab } from './DashboardTab';
import { DashboardTabButton } from './DashboardTabButton';
import { PersonalDashboard, type BuiltInRoadmap } from './PersonalDashboard';
import { TeamDashboard } from './TeamDashboard';
import { getUser } from '../../lib/jwt';
import { useParams } from '../../hooks/use-params';
import type { QuestionGroupType } from '../../lib/question-group';
import type { GuideFileType } from '../../lib/guide';
import type { VideoFileType } from '../../lib/video';
type DashboardPageProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];
builtInSkillRoadmaps?: BuiltInRoadmap[];
builtInBestPractices?: BuiltInRoadmap[];
isTeamPage?: boolean;
questionGroups?: QuestionGroupType[];
guides?: GuideFileType[];
videos?: VideoFileType[];
};
export function DashboardPage(props: DashboardPageProps) {
@@ -23,6 +30,9 @@ export function DashboardPage(props: DashboardPageProps) {
builtInBestPractices,
builtInSkillRoadmaps,
isTeamPage = false,
questionGroups,
guides,
videos,
} = props;
const currentUser = getUser();
@@ -66,78 +76,79 @@ export function DashboardPage(props: DashboardPageProps) {
: '/images/default-avatar.png';
return (
<div className="min-h-screen bg-gray-50 pb-20 pt-8">
<div className="container">
<div className="mb-6 flex flex-wrap items-center gap-1.5 sm:mb-8">
<DashboardTab
label="Personal"
isActive={!selectedTeamId && !isTeamPage}
href="/dashboard"
avatar={userAvatar}
/>
{isLoading && (
<>
<DashboardTabSkeleton />
<DashboardTabSkeleton />
</>
)}
{!isLoading && (
<>
{teamList.map((team) => {
const { avatar } = team;
const avatarUrl = avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
return (
<DashboardTab
key={team._id}
label={team.name}
isActive={team._id === selectedTeamId}
{...(team.status === 'invited'
? {
href: `/respond-invite?i=${team.memberId}`,
}
: {
href: `/team?t=${team._id}`,
})}
avatar={avatarUrl}
/>
);
})}
<DashboardTab
label="+ Create Team"
isActive={false}
href="/team/new"
className="border border-dashed border-gray-300 bg-transparent px-3 text-[13px] text-sm text-gray-500 hover:border-gray-600 hover:text-black"
/>
</>
)}
<>
<div
className={cn('bg-slate-900', {
'striped-loader-slate': isLoading,
})}
>
<div className="bg-slate-800/30 py-5 min-h-[70px]">
<div className="container flex flex-wrap items-center gap-1.5">
{!isLoading && (
<>
<DashboardTabButton
label="Personal"
isActive={!selectedTeamId && !isTeamPage}
href="/dashboard"
avatar={userAvatar}
/>
{teamList.map((team) => {
const { avatar } = team;
const avatarUrl = avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
return (
<DashboardTabButton
key={team._id}
label={team.name}
isActive={team._id === selectedTeamId}
{...(team.status === 'invited'
? {
href: `/respond-invite?i=${team.memberId}`,
}
: {
href: `/team?t=${team._id}`,
})}
avatar={avatarUrl}
/>
);
})}
<DashboardTabButton
label="+ Create Team"
isActive={false}
href="/team/new"
className="border border-dashed border-slate-700 bg-transparent px-3 text-[13px] text-sm text-gray-500 hover:border-solid hover:border-slate-700 hover:text-gray-400"
/>
</>
)}
</div>
</div>
</div>
<div className="">
{!selectedTeamId && !isTeamPage && (
<PersonalDashboard
builtInRoleRoadmaps={builtInRoleRoadmaps}
builtInSkillRoadmaps={builtInSkillRoadmaps}
builtInBestPractices={builtInBestPractices}
/>
<div className="bg-slate-900">
<PersonalDashboard
builtInRoleRoadmaps={builtInRoleRoadmaps}
builtInSkillRoadmaps={builtInSkillRoadmaps}
builtInBestPractices={builtInBestPractices}
questionGroups={questionGroups}
guides={guides}
videos={videos}
/>
</div>
)}
{(selectedTeamId || isTeamPage) && (
<TeamDashboard
builtInRoleRoadmaps={builtInRoleRoadmaps!}
builtInSkillRoadmaps={builtInSkillRoadmaps!}
teamId={selectedTeamId!}
/>
<div className="container">
<TeamDashboard
builtInRoleRoadmaps={builtInRoleRoadmaps!}
builtInSkillRoadmaps={builtInSkillRoadmaps!}
teamId={selectedTeamId!}
/>
</div>
)}
</div>
</div>
);
}
function DashboardTabSkeleton() {
return (
<div className="h-[30px] w-[114px] animate-pulse rounded-md border bg-white"></div>
</>
);
}

View File

@@ -1,6 +1,5 @@
import { getPercentage } from '../../helper/number';
import { getPercentage } from '../../lib/number';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import { ArrowUpRight, ExternalLink } from 'lucide-react';
type DashboardProgressCardProps = {
progress: UserProgress;

View File

@@ -11,7 +11,7 @@ type DashboardTabProps = {
icon?: ReactNode;
};
export function DashboardTab(props: DashboardTabProps) {
export function DashboardTabButton(props: DashboardTabProps) {
const { isActive, onClick, label, className, href, avatar, icon } = props;
const Slot = href ? 'a' : 'button';
@@ -20,8 +20,10 @@ export function DashboardTab(props: DashboardTabProps) {
<Slot
onClick={onClick}
className={cn(
'flex h-[30px] shrink-0 items-center gap-1 rounded-md border bg-white p-1.5 px-2 text-sm leading-none text-gray-600',
isActive ? 'border-gray-500 bg-gray-200 text-gray-900' : '',
'flex h-[30px] shrink-0 items-center gap-1 rounded-md border border-slate-700 bg-slate-800 p-1.5 pl-2 pr-3 text-sm leading-none text-gray-400 transition-colors hover:bg-slate-700',
isActive
? 'border-slate-200 bg-slate-200 text-gray-900 hover:bg-slate-200'
: '',
className,
)}
{...(href ? { href } : {})}
@@ -30,7 +32,7 @@ export function DashboardTab(props: DashboardTabProps) {
<img
src={avatar}
alt="avatar"
className="h-4 w-4 mr-0.5 rounded-full object-cover"
className="mr-0.5 h-4 w-4 rounded-full object-cover"
/>
)}
{icon}

View File

@@ -1,23 +1,47 @@
import { type JSXElementConstructor, useEffect, useState } from 'react';
import { httpGet } from '../../lib/http';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import type { PageType } from '../CommandMenu/CommandMenu';
import { useToast } from '../../hooks/use-toast';
import { getCurrentPeriod } from '../../lib/date';
import { ListDashboardCustomProgress } from './ListDashboardCustomProgress';
import { RecommendedRoadmaps } from './RecommendedRoadmaps';
import { ProgressStack } from './ProgressStack';
import { useStore } from '@nanostores/react';
import { $accountStreak, type StreakResponse } from '../../stores/streak';
import { CheckEmoji } from '../ReactIcons/CheckEmoji.tsx';
import { ConstructionEmoji } from '../ReactIcons/ConstructionEmoji.tsx';
import { BookEmoji } from '../ReactIcons/BookEmoji.tsx';
import { DashboardAiRoadmaps } from './DashboardAiRoadmaps.tsx';
import {
ChartColumn,
CheckSquare,
FolderGit2,
SquarePen,
Zap,
type LucideIcon
} from 'lucide-react';
import { useEffect, useState } from 'react';
import type { AllowedProfileVisibility } from '../../api/user.ts';
import { PencilIcon, type LucideIcon } from 'lucide-react';
import { useToast } from '../../hooks/use-toast';
import { cn } from '../../lib/classname.ts';
import type { GuideFileType } from '../../lib/guide';
import { httpGet } from '../../lib/http';
import type { QuestionGroupType } from '../../lib/question-group';
import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts';
import type { VideoFileType } from '../../lib/video';
import { $accountStreak, type StreakResponse } from '../../stores/streak';
import type { PageType } from '../CommandMenu/CommandMenu';
import { FeaturedGuideList } from '../FeaturedGuides/FeaturedGuideList';
import { FeaturedVideoList } from '../FeaturedVideos/FeaturedVideoList';
import {
FavoriteRoadmaps,
type AIRoadmapType,
} from '../HeroSection/FavoriteRoadmaps.tsx';
import { HeroRoadmap } from '../HeroSection/HeroRoadmap.tsx';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
const projectGroups = [
{
title: 'Frontend',
id: 'frontend',
},
{
title: 'Backend',
id: 'backend',
},
{
title: 'DevOps',
id: 'devops',
},
];
type UserDashboardResponse = {
name: string;
@@ -28,11 +52,7 @@ type UserDashboardResponse = {
profileVisibility: AllowedProfileVisibility;
progresses: UserProgress[];
projects: ProjectStatusDocument[];
aiRoadmaps: {
id: string;
title: string;
slug: string;
}[];
aiRoadmaps: AIRoadmapType[];
topicDoneToday: number;
};
@@ -42,6 +62,7 @@ export type BuiltInRoadmap = {
title: string;
description: string;
isFavorite?: boolean;
isNew?: boolean;
relatedRoadmapIds?: string[];
renderer?: AllowedRoadmapRenderer;
metadata?: Record<string, any>;
@@ -51,16 +72,162 @@ type PersonalDashboardProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];
builtInSkillRoadmaps?: BuiltInRoadmap[];
builtInBestPractices?: BuiltInRoadmap[];
questionGroups?: QuestionGroupType[];
guides?: GuideFileType[];
videos?: VideoFileType[];
};
type DashboardStatItemProps = {
icon: LucideIcon;
iconClassName: string;
value: number;
label: string;
isLoading: boolean;
};
function DashboardStatItem(props: DashboardStatItemProps) {
const { icon: Icon, iconClassName, value, label, isLoading } = props;
return (
<div
className={cn(
'flex items-center gap-1.5 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3',
{
'striped-loader-slate striped-loader-slate-fast text-transparent':
isLoading,
},
)}
>
<Icon
size={16}
className={cn(iconClassName, { 'text-transparent': isLoading })}
/>
<span>
<span className="tabular-nums">{value}</span> {label}
</span>
</div>
);
}
type ProfileButtonProps = {
isLoading: boolean;
name?: string;
username?: string;
avatar?: string;
};
function PersonalProfileButton(props: ProfileButtonProps) {
const { isLoading, name, username, avatar } = props;
if (isLoading || !username) {
return (
<a
href="/account/update-profile"
className={cn(
'flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 font-medium outline-slate-700 hover:bg-slate-800 hover:outline-slate-400',
{
'striped-loader-slate striped-loader-slate-fast text-transparent':
isLoading,
'bg-blue-500/10 text-blue-500 hover:bg-blue-500/20': !isLoading,
},
)}
>
<CheckSquare className="h-4 w-4" strokeWidth={2.5} />
Set up your profile
</a>
);
}
return (
<div className="flex gap-1.5">
<a
href={`/u/${username}`}
className="flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 text-slate-300 transition-colors hover:bg-slate-800/70"
>
<img
src={avatar}
alt={name || 'Profile'}
className="h-5 w-5 rounded-full ring-1 ring-slate-700"
/>
<span className="font-medium">Visit Profile</span>
</a>
<a
href="/account/update-profile"
className="flex items-center gap-2 rounded-lg bg-slate-800/50 py-2 pl-3 pr-3 text-slate-400 transition-colors hover:bg-slate-800/70 hover:text-slate-300"
title="Edit Profile"
>
<SquarePen className="h-4 w-4" />
</a>
</div>
);
}
type DashboardStatsProps = {
profile: ProfileButtonProps;
accountStreak?: StreakResponse;
topicsDoneToday?: number;
finishedProjectsCount?: number;
isLoading: boolean;
};
function DashboardStats(props: DashboardStatsProps) {
const {
accountStreak,
topicsDoneToday = 0,
finishedProjectsCount = 0,
isLoading,
profile,
} = props;
return (
<div className="container mb-3 flex flex-col gap-4 pb-2 pt-6 text-sm text-slate-400 sm:flex-row sm:items-center sm:justify-between">
<div className="flex w-full flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<PersonalProfileButton
isLoading={isLoading}
name={profile.name}
username={profile.username}
avatar={profile.avatar}
/>
<div className="hidden flex-wrap items-center gap-2 md:flex">
<DashboardStatItem
icon={Zap}
iconClassName="text-yellow-500"
value={accountStreak?.count || 0}
label="day streak"
isLoading={isLoading}
/>
<DashboardStatItem
icon={ChartColumn}
iconClassName="text-green-500"
value={topicsDoneToday}
label="learnt today"
isLoading={isLoading}
/>
<DashboardStatItem
icon={FolderGit2}
iconClassName="text-blue-500"
value={finishedProjectsCount}
label="projects finished"
isLoading={isLoading}
/>
</div>
</div>
</div>
);
}
export function PersonalDashboard(props: PersonalDashboardProps) {
const {
builtInRoleRoadmaps = [],
builtInBestPractices = [],
builtInSkillRoadmaps = [],
questionGroups = [],
guides = [],
videos = [],
} = props;
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
const [personalDashboardDetails, setPersonalDashboardDetails] =
useState<UserDashboardResponse>();
@@ -138,7 +305,9 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
return () => window.removeEventListener('refresh-favorites', loadProgress);
}, []);
const learningRoadmapsToShow = (personalDashboardDetails?.progresses || [])
const learningRoadmapsToShow: UserProgress[] = (
personalDashboardDetails?.progresses || []
)
.filter((progress) => !progress.isCustomResource)
.sort((a, b) => {
const updatedAtA = new Date(a.updatedAt);
@@ -156,7 +325,10 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
});
const aiGeneratedRoadmaps = personalDashboardDetails?.aiRoadmaps || [];
const customRoadmaps = (personalDashboardDetails?.progresses || [])
const customRoadmaps: UserProgress[] = (
personalDashboardDetails?.progresses || []
)
.filter((progress) => progress.isCustomResource)
.sort((a, b) => {
const updatedAtA = new Date(a.updatedAt);
@@ -169,43 +341,6 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
const allRoadmapsAndBestPractices = [
...builtInRoleRoadmaps,
...builtInSkillRoadmaps,
...builtInBestPractices,
];
const relatedRoadmapIds = allRoadmapsAndBestPractices
// take the ones that user is learning
.filter((roadmap) =>
learningRoadmapsToShow?.some(
(learningRoadmap) => learningRoadmap.resourceId === roadmap.id,
),
)
.flatMap((roadmap) => roadmap.relatedRoadmapIds)
// remove the ones that user is already learning or has bookmarked
.filter(
(roadmapId) =>
!learningRoadmapsToShow.some((lr) => lr.resourceId === roadmapId),
);
const recommendedRoadmapIds = new Set(
relatedRoadmapIds.length === 0
? [
'frontend',
'backend',
'devops',
'ai-data-scientist',
'full-stack',
'api-design',
]
: relatedRoadmapIds,
);
const recommendedRoadmaps = allRoadmapsAndBestPractices.filter((roadmap) =>
recommendedRoadmapIds.has(roadmap.id),
);
const enrichedProjects = personalDashboardDetails?.projects
.map((project) => {
const projectDetail = projectDetails.find(
@@ -232,165 +367,200 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
const { username } = personalDashboardDetails || {};
return (
<section>
{isLoading ? (
<div className="h-7 w-1/4 animate-pulse rounded-lg bg-gray-200"></div>
) : (
<div className="flex flex-col items-start justify-between gap-1 sm:flex-row sm:items-center">
<h2 className="text-lg font-medium">
Hi {name}, good {getCurrentPeriod()}!
</h2>
<a
href="/home"
className="rounded-full bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-300 hover:text-black"
>
Visit Homepage
</a>
</div>
)}
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-4">
{isLoading ? (
<>
<DashboardCardSkeleton />
<DashboardCardSkeleton />
<DashboardCardSkeleton />
<DashboardCardSkeleton />
</>
) : (
<>
<DashboardCard
imgUrl={avatarLink}
title={name!}
description={
username ? 'View your profile' : 'Setup your profile'
}
href={username ? `/u/${username}` : '/account/update-profile'}
{...(username && {
externalLinkIcon: PencilIcon,
externalLinkHref: '/account/update-profile',
externalLinkText: 'Edit',
})}
className={
!username
? 'border-dashed border-gray-500 bg-gray-100 hover:border-gray-500 hover:bg-gray-200'
: ''
}
/>
<DashboardCard
icon={BookEmoji}
title="Visit Roadmaps"
description="Learn new skills"
href="/roadmaps"
/>
<DashboardCard
icon={ConstructionEmoji}
title="Build Projects"
description="Practice what you learn"
href="/projects"
/>
<DashboardCard
icon={CheckEmoji}
title="Best Practices"
description="Do things the right way"
href="/best-practices"
/>
</>
)}
</div>
<ProgressStack
progresses={learningRoadmapsToShow}
projects={enrichedProjects || []}
<div>
<DashboardStats
profile={{
name,
username,
avatar: avatarLink,
isLoading,
}}
isLoading={isLoading}
accountStreak={accountStreak}
topicDoneToday={personalDashboardDetails?.topicDoneToday || 0}
topicsDoneToday={personalDashboardDetails?.topicDoneToday}
finishedProjectsCount={
enrichedProjects?.filter((p) => p.submittedAt && p.repositoryUrl)
.length
}
/>
<ListDashboardCustomProgress
progresses={customRoadmaps}
<FavoriteRoadmaps
progress={learningRoadmapsToShow}
customRoadmaps={customRoadmaps}
aiRoadmaps={aiGeneratedRoadmaps}
projects={enrichedProjects || []}
isLoading={isLoading}
/>
<DashboardAiRoadmaps
roadmaps={aiGeneratedRoadmaps}
isLoading={isLoading}
/>
<div className="bg-gradient-to-b from-slate-900 to-black pb-12">
<div className="relative mt-6 border-t border-t-[#1e293c] pt-12">
<div className="container">
<h2
id="role-based-roadmaps"
className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2"
>
Role Based Roadmaps
</h2>
<RecommendedRoadmaps
roadmaps={recommendedRoadmaps}
isLoading={isLoading}
/>
</section>
);
}
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
{builtInRoleRoadmaps.map((roadmap) => {
const roadmapProgress = learningRoadmapsToShow.find(
(lr) => lr.resourceId === roadmap.id,
);
type DashboardCardProps = {
icon?: JSXElementConstructor<any>;
imgUrl?: string;
title: string;
description: string;
href: string;
externalLinkIcon?: LucideIcon;
externalLinkText?: string;
externalLinkHref?: string;
className?: string;
};
const percentageDone =
(((roadmapProgress?.skipped || 0) +
(roadmapProgress?.done || 0)) /
(roadmapProgress?.total || 1)) *
100;
function DashboardCard(props: DashboardCardProps) {
const {
icon: Icon,
imgUrl,
title,
description,
href,
externalLinkHref,
externalLinkIcon: ExternalLinkIcon,
externalLinkText,
className,
} = props;
return (
<div className={cn('relative overflow-hidden', className)}>
<a
href={href}
className="flex flex-col rounded-lg border border-gray-300 bg-white hover:border-gray-400 hover:bg-gray-50"
>
{Icon && (
<div className="px-4 pb-3 pt-4">
<Icon className="size-6" />
return (
<HeroRoadmap
key={roadmap.id}
resourceId={roadmap.id}
resourceType="roadmap"
resourceTitle={roadmap.title}
isFavorite={roadmap.isFavorite}
percentageDone={percentageDone}
isNew={roadmap.isNew}
url={`/${roadmap.id}`}
/>
);
})}
</div>
</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>
{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 className="relative mt-12 border-t border-t-[#1e293c] pt-12">
<div className="container">
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
Skill Based Roadmaps
</h2>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
{builtInSkillRoadmaps.map((roadmap) => {
const roadmapProgress = learningRoadmapsToShow.find(
(lr) => lr.resourceId === roadmap.id,
);
const percentageDone =
(((roadmapProgress?.skipped || 0) +
(roadmapProgress?.done || 0)) /
(roadmapProgress?.total || 1)) *
100;
return (
<HeroRoadmap
key={roadmap.id}
resourceId={roadmap.id}
resourceType="roadmap"
resourceTitle={roadmap.title}
isFavorite={roadmap.isFavorite}
percentageDone={percentageDone}
isNew={roadmap.isNew}
url={`/${roadmap.id}`}
/>
);
})}
</div>
</div>
</div>
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
<div className="container">
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
Project Ideas
</h2>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
{projectGroups.map((projectGroup) => {
return (
<HeroRoadmap
percentageDone={0}
key={projectGroup.id}
resourceId={projectGroup.id}
resourceType="roadmap"
resourceTitle={projectGroup.title}
url={`/${projectGroup.id}/projects`}
allowFavorite={false}
/>
);
})}
</div>
</div>
</div>
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
<div className="container">
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
Best Practices
</h2>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
{builtInBestPractices.map((roadmap) => {
const roadmapProgress = learningRoadmapsToShow.find(
(lr) => lr.resourceId === roadmap.id,
);
const percentageDone =
(((roadmapProgress?.skipped || 0) +
(roadmapProgress?.done || 0)) /
(roadmapProgress?.total || 1)) *
100;
return (
<HeroRoadmap
key={roadmap.id}
resourceId={roadmap.id}
resourceType="best-practice"
resourceTitle={roadmap.title}
isFavorite={roadmap.isFavorite}
percentageDone={percentageDone}
isNew={roadmap.isNew}
url={`/best-practices/${roadmap.id}`}
/>
);
})}
</div>
</div>
</div>
<div className="relative mt-12 border-t border-t-[#1e293c] pt-12">
<div className="container">
<h2 className="text-md font-regular absolute -top-[17px] left-4 flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2">
Questions
</h2>
<div className="grid grid-cols-1 gap-3 px-2 sm:grid-cols-2 sm:px-0 lg:grid-cols-3">
{questionGroups.map((questionGroup) => {
return (
<HeroRoadmap
percentageDone={0}
key={questionGroup.id}
resourceId={questionGroup.id}
resourceType="roadmap"
resourceTitle={questionGroup.frontmatter.briefTitle}
url={`/questions/${questionGroup.id}`}
allowFavorite={false}
isNew={questionGroup.frontmatter.isNew}
/>
);
})}
</div>
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-5 bg-gray-50 px-4 py-5 sm:gap-16 sm:px-0 sm:py-16">
<FeaturedGuideList
heading="Guides"
guides={guides}
questions={questionGroups
.filter((questionGroup) => questionGroup.frontmatter.authorId)
.slice(0, 7)}
/>
<FeaturedVideoList heading="Videos" videos={videos} />
</div>
</div>
);
}
function DashboardCardSkeleton() {
return (
<div className="h-[128px] animate-pulse rounded-lg border border-gray-300 bg-white"></div>
);
}

View File

@@ -6,80 +6,24 @@ import { isMobileScreen } from '../lib/is-mobile.ts';
type FeatureAnnouncementProps = {};
export function FeatureAnnouncement(props: FeatureAnnouncementProps) {
const [isPlaying, setIsPlaying] = useState(false);
const videoModal = (
<Modal
onClose={() => setIsPlaying(false)}
bodyClassName={'h-auto overflow-hidden'}
wrapperClassName={'md:max-w-3xl lg:max-w-4xl xl:max-w-5xl'}
>
<div className="text-balance bg-gradient-to-r from-gray-100 px-4 py-2 text-left text-sm md:py-3 lg:text-base">
<span
className="relative -top-px mr-1.5 rounded bg-blue-300 px-1.5 py-0.5 text-xs font-semibold uppercase text-gray-800"
style={{ lineHeight: '1.5' }}
>
New
</span>
Projects are live on the{' '}
<a
href={'/projects'}
className="font-medium text-blue-500 underline underline-offset-2"
>
several of our roadmaps
</a>
<span className={'hidden md:inline'}>
{' '}
and are coming soon on the others
</span>
<PartyPopper className="relative -top-[3px] ml-2 inline-block h-5 w-5 text-blue-500 md:ml-1 md:h-6 md:w-6" />
</div>
<div
className="iframe-container"
style={{
position: 'relative',
paddingBottom: '56.25%',
height: 0,
overflow: 'hidden',
}}
>
{/*https://www.youtube.com/embed/?playsinline=1&disablekb=1&&iv_load_policy=3&cc_load_policy=0&controls=0&rel=0&autoplay=1&mute=1&origin=https%3A%2F%2Fytch.xyz&widgetid=1*/}
<iframe
src="https://www.youtube.com/embed/9lS3slfJ0x0?start=31&autoplay=1&disablekb=1&rel=0&cc_load_policy=0&rel=0&autoplay=1&origin=https%3A%2F%2Froadmap.sh&widgetid=1&showinfo=0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}
/>
</div>
</Modal>
);
return null;
return (
<>
{isPlaying && videoModal}
<button
<a
className="rounded-md border border-dashed border-purple-600 px-3 py-1.5 text-purple-400 transition-colors hover:border-purple-400 hover:text-purple-200"
onClick={() => {
setIsPlaying(true);
}}
href="/courses/sql"
>
<span className="relative sm:-top-[1px] mr-1 text-xs font-semibold uppercase text-white">
<PlayCircle className="inline-block h-4 w-4 relative -top-[2px] mr-1" />
Watch
<PartyPopper className="inline-block h-4 w-4 relative -top-[2px] mr-1" />
Courses
</span>{' '}
<span className={'hidden sm:inline'}>
Practice your skills with projects
Our first paid course about SQL is now live!
</span>
<span className={'inline text-sm sm:hidden'}>
Build projects to skill up
Our SQL course is now live!
</span>
</button>
</a>
</>
);
}

View File

@@ -1,47 +0,0 @@
---
import type { GuideFileType } from '../lib/guide';
import GuideListItem from './GuideListItem.astro';
import { QuestionGroupType } from '../lib/question-group';
export interface Props {
heading: string;
guides: GuideFileType[];
questions: QuestionGroupType[];
}
const { heading, guides, questions = [] } = Astro.props;
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
...guides,
...questions,
].sort((a, b) => {
const aDate = new Date(a.frontmatter.date);
const bDate = new Date(b.frontmatter.date);
return bDate.getTime() - aDate.getTime();
});
---
<div class='container'>
<h2 class='block text-2xl font-bold sm:text-3xl'>{heading}</h2>
<div class='mt-3 sm:my-5'>
{sortedGuides.map((guide) => <GuideListItem guide={guide} />)}
</div>
<a
href='/guides'
class='hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline'
>
View All Guides &rarr;
</a>
<div class='mt-3 block sm:hidden'>
<a
href='/guides'
class='font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50'
>
View All Guides &nbsp;&rarr;
</a>
</div>
</div>

View File

@@ -0,0 +1,51 @@
import type { GuideFileType } from '../../lib/guide';
import type { QuestionGroupType } from '../../lib/question-group';
import { GuideListItem } from './GuideListItem';
export interface FeaturedGuidesProps {
heading: string;
guides: GuideFileType[];
questions: QuestionGroupType[];
}
export function FeaturedGuideList(props: FeaturedGuidesProps) {
const { heading, guides, questions = [] } = props;
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
...guides,
...questions,
].sort((a, b) => {
const aDate = new Date(a.frontmatter.date as string);
const bDate = new Date(b.frontmatter.date as string);
return bDate.getTime() - aDate.getTime();
});
return (
<div className="container">
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
<div className="mt-3 sm:my-5">
{sortedGuides.map((guide) => (
<GuideListItem key={guide.id} guide={guide} />
))}
</div>
<a
href="/guides"
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
>
View All Guides &rarr;
</a>
<div className="mt-3 block sm:hidden">
<a
href="/guides"
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
>
View All Guides &nbsp;&rarr;
</a>
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
import type { GuideFileType, GuideFrontmatter } from '../../lib/guide';
import { type QuestionGroupType } from '../../lib/question-group';
export interface GuideListItemProps {
guide: GuideFileType | QuestionGroupType;
}
function isQuestionGroupType(
guide: GuideFileType | QuestionGroupType,
): guide is QuestionGroupType {
return (guide as QuestionGroupType).questions !== undefined;
}
export function GuideListItem(props: GuideListItemProps) {
const { guide } = props;
const { frontmatter, id } = guide;
let pageUrl = '';
let guideType = '';
if (isQuestionGroupType(guide)) {
pageUrl = `/questions/${id}`;
guideType = 'Questions';
} else {
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
guideType = (frontmatter as GuideFrontmatter).type;
}
return (
<a
className="text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600"
href={pageUrl}
>
<span className="text-sm transition-transform group-hover:translate-x-2 md:text-base">
{frontmatter.title}
{frontmatter.isNew && (
<span className="ml-2.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900">
New
<span className="hidden sm:inline">
&nbsp;&middot;&nbsp;
{new Date(frontmatter.date || '').toLocaleString('default', {
month: 'long',
})}
</span>
</span>
)}
</span>
<span className="hidden text-xs capitalize text-gray-500 sm:block">
{guideType}
</span>
<span className="block text-xs text-gray-400 sm:hidden"> &raquo;</span>
</a>
);
}

View File

@@ -1,35 +0,0 @@
---
import type { VideoFileType } from '../lib/video';
import VideoListItem from './VideoListItem.astro';
export interface Props {
heading: string;
videos: VideoFileType[];
}
const { heading, videos } = Astro.props;
---
<div class='container'>
<h2 class='text-2xl sm:text-3xl font-bold block'>{heading}</h2>
<div class='mt-3 sm:my-5'>
{videos.map((video) => <VideoListItem video={video} />)}
</div>
<a
href='/videos'
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-gradient-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white'
>
View All Videos &rarr;
</a>
<div class='block sm:hidden mt-3'>
<a
href='/videos'
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50'
>
View All Videos &nbsp;&rarr;
</a>
</div>
</div>

View File

@@ -0,0 +1,39 @@
import type { VideoFileType } from '../../lib/video';
import { VideoListItem } from './VideoListItem';
export interface FeaturedVideoListProps {
heading: string;
videos: VideoFileType[];
}
export function FeaturedVideoList(props: FeaturedVideoListProps) {
const { heading, videos } = props;
return (
<div className="container">
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
<div className="mt-3 sm:my-5">
{videos.map((video) => (
<VideoListItem key={video.id} video={video} />
))}
</div>
<a
href="/videos"
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
>
View All Videos &rarr;
</a>
<div className="mt-3 block sm:hidden">
<a
href="/videos"
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
>
View All Videos &nbsp;&rarr;
</a>
</div>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import type { VideoFileType } from '../../lib/video';
export interface VideoListItemProps {
video: VideoFileType;
}
export function VideoListItem(props: VideoListItemProps) {
const { video } = props;
const { frontmatter, id } = video;
return (
<a
className="block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b"
href={`/videos/${id}`}
>
<span className="group-hover:translate-x-2 transition-transform">
{frontmatter.title}
{frontmatter.isNew && (
<span className="bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5">
New
<span className="hidden sm:inline">
&middot;
{new Date(frontmatter.date).toLocaleString('default', {
month: 'long',
})}
</span>
</span>
)}
</span>
<span className="capitalize text-gray-500 text-xs hidden sm:block">
{frontmatter.duration}
</span>
<span className="text-gray-400 text-xs block sm:hidden"> &raquo;</span>
</a>
);
}

View File

@@ -0,0 +1,172 @@
import { SearchIcon, WandIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { cn } from '../../lib/classname';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
import { UserCoursesList } from './UserCoursesList';
import { FineTuneCourse } from './FineTuneCourse';
import {
clearFineTuneData,
getCourseFineTuneData,
getLastSessionId,
storeFineTuneData,
} from '../../lib/ai';
export const difficultyLevels = [
'beginner',
'intermediate',
'advanced',
] as const;
export type DifficultyLevel = (typeof difficultyLevels)[number];
type AICourseProps = {};
export function AICourse(props: AICourseProps) {
const [keyword, setKeyword] = useState('');
const [difficulty, setDifficulty] = useState<DifficultyLevel>('beginner');
const [hasFineTuneData, setHasFineTuneData] = useState(false);
const [about, setAbout] = useState('');
const [goal, setGoal] = useState('');
const [customInstructions, setCustomInstructions] = useState('');
useEffect(() => {
const lastSessionId = getLastSessionId();
if (!lastSessionId) {
return;
}
const fineTuneData = getCourseFineTuneData(lastSessionId);
if (!fineTuneData) {
return;
}
setAbout(fineTuneData.about);
setGoal(fineTuneData.goal);
setCustomInstructions(fineTuneData.customInstructions);
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && keyword.trim()) {
onSubmit();
}
};
function onSubmit() {
if (!isLoggedIn()) {
showLoginPopup();
return;
}
let sessionId = '';
if (hasFineTuneData) {
clearFineTuneData();
sessionId = storeFineTuneData({
about,
goal,
customInstructions,
});
}
window.location.href = `/ai-tutor/search?term=${encodeURIComponent(keyword)}&difficulty=${difficulty}&id=${sessionId}`;
}
return (
<section className="flex flex-grow flex-col bg-gray-100">
<div className="container mx-auto flex max-w-3xl flex-col py-24 max-sm:py-4">
<h1 className="mb-2.5 text-center text-4xl font-bold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
Learn anything with AI
</h1>
<p className="mb-6 text-center text-lg text-gray-600 max-sm:hidden max-sm:text-left max-sm:text-sm">
Enter a topic below to generate a personalized course for it
</p>
<div className="rounded-lg border border-gray-200 bg-white p-6 max-sm:p-4">
<form
className="flex flex-col gap-5"
onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}
>
<div className="flex flex-col">
<label
htmlFor="keyword"
className="mb-2.5 text-sm font-medium text-gray-700"
>
Course Topic
</label>
<div className="relative">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
<SearchIcon size={18} />
</div>
<input
id="keyword"
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="e.g., Algebra, JavaScript, Photography"
className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-500 max-sm:placeholder:text-base"
maxLength={50}
/>
</div>
</div>
<div className="flex flex-col">
<label className="mb-2.5 text-sm font-medium text-gray-700">
Difficulty Level
</label>
<div className="flex gap-2 max-sm:flex-col max-sm:gap-1">
{difficultyLevels.map((level) => (
<button
key={level}
type="button"
onClick={() => setDifficulty(level)}
className={cn(
'rounded-md border px-4 py-2 capitalize max-sm:text-sm',
difficulty === level
? 'border-gray-800 bg-gray-800 text-white'
: 'border-gray-200 bg-gray-100 text-gray-700 hover:bg-gray-200',
)}
>
{level}
</button>
))}
</div>
</div>
<FineTuneCourse
hasFineTuneData={hasFineTuneData}
setHasFineTuneData={setHasFineTuneData}
about={about}
goal={goal}
customInstructions={customInstructions}
setAbout={setAbout}
setGoal={setGoal}
setCustomInstructions={setCustomInstructions}
/>
<button
type="submit"
disabled={!keyword.trim()}
className={cn(
'mt-2 flex items-center justify-center rounded-md px-4 py-2 font-medium text-white transition-colors max-sm:text-sm',
!keyword.trim()
? 'cursor-not-allowed bg-gray-400'
: 'bg-black hover:bg-gray-800',
)}
>
<WandIcon size={18} className="mr-2" />
Generate Course
</button>
</form>
</div>
<div className="mt-8 min-h-[200px]">
<UserCoursesList />
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,116 @@
import { MoreVertical, Play, Trash2 } from 'lucide-react';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { useKeydown } from '../../hooks/use-keydown';
import { useToast } from '../../hooks/use-toast';
import { useMutation } from '@tanstack/react-query';
import { queryClient } from '../../stores/query-client';
import { httpDelete } from '../../lib/query-http';
type AICourseActionsType = {
courseSlug: string;
onDeleted?: () => void;
};
export function AICourseActions(props: AICourseActionsType) {
const { courseSlug, onDeleted } = props;
const toast = useToast();
const dropdownRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const { mutate: deleteCourse, isPending: isDeleting } = useMutation(
{
mutationFn: async () => {
return httpDelete(`/v1-delete-ai-course/${courseSlug}`);
},
onSuccess: () => {
toast.success('Course deleted');
queryClient.invalidateQueries({
predicate: (query) => query.queryKey?.[0] === 'user-ai-courses',
});
onDeleted?.();
},
onError: (error) => {
toast.error(error?.message || 'Failed to delete course');
},
},
queryClient,
);
useOutsideClick(dropdownRef, () => {
setIsOpen(false);
});
useKeydown('Escape', () => {
setIsOpen(false);
});
return (
<div className="relative h-full" ref={dropdownRef}>
<button
className="h-full text-gray-400 hover:text-gray-700"
onClick={(e) => {
e.stopPropagation();
setIsOpen(!isOpen);
}}
>
<MoreVertical size={16} />
</button>
{isOpen && (
<div className="absolute right-0 top-8 z-10 w-48 overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg">
<a
href={`/ai-tutor/${courseSlug}`}
className="flex w-full items-center gap-1.5 p-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-black disabled:cursor-not-allowed disabled:opacity-70"
>
<Play className="h-3.5 w-3.5" />
Start Course
</a>
{!isConfirming && (
<button
className="flex w-full items-center gap-1.5 p-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-black disabled:cursor-not-allowed disabled:opacity-70"
onClick={() => setIsConfirming(true)}
disabled={isDeleting}
>
{!isDeleting ? (
<>
<Trash2 className="h-3.5 w-3.5" />
Delete Course
</>
) : (
'Deleting...'
)}
</button>
)}
{isConfirming && (
<span className="flex w-full items-center justify-between gap-1.5 p-2 text-sm font-medium text-gray-500 hover:bg-gray-100 hover:text-black disabled:cursor-not-allowed disabled:opacity-70">
Are you sure?
<div className="flex items-center gap-2">
<button
onClick={() => {
setIsConfirming(false);
deleteCourse();
}}
disabled={isDeleting}
className="text-red-500 underline hover:text-red-800"
>
Yes
</button>
<button
onClick={() => setIsConfirming(false)}
className="text-red-500 underline hover:text-red-800"
>
No
</button>
</div>
</span>
)}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,82 @@
import type { AICourseWithLessonCount } from '../../queries/ai-course';
import type { DifficultyLevel } from './AICourse';
import { BookOpen } from 'lucide-react';
import { AICourseActions } from './AICourseActions';
type AICourseCardProps = {
course: AICourseWithLessonCount;
};
export function AICourseCard(props: AICourseCardProps) {
const { course } = props;
// Format date if available
const formattedDate = course.createdAt
? new Date(course.createdAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})
: null;
// Map difficulty to color
const difficultyColor =
{
beginner: 'text-green-700',
intermediate: 'text-blue-700',
advanced: 'text-purple-700',
}[course.difficulty as DifficultyLevel] || 'text-gray-700';
// Calculate progress percentage
const totalTopics = course.lessonCount || 0;
const completedTopics = course.done?.length || 0;
const progressPercentage =
totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0;
return (
<div className="relative">
<a
href={`/ai-tutor/${course.slug}`}
className="hover:border-gray-3 00 group relative flex w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:bg-gray-50"
>
<div className="flex items-center justify-between">
<span
className={`rounded-full text-xs font-medium capitalize opacity-80 ${difficultyColor}`}
>
{course.difficulty}
</span>
</div>
<h3 className="my-2 text-base font-semibold text-gray-900">
{course.title}
</h3>
<div className="mt-auto flex items-center justify-between pt-2">
<div className="flex items-center text-xs text-gray-600">
<BookOpen className="mr-1 h-3.5 w-3.5" />
<span>{totalTopics} lessons</span>
</div>
{totalTopics > 0 && (
<div className="flex items-center">
<div className="mr-2 h-1.5 w-16 overflow-hidden rounded-full bg-gray-200">
<div
className="h-full rounded-full bg-blue-600"
style={{ width: `${progressPercentage}%` }}
/>
</div>
<span className="text-xs font-medium text-gray-700">
{progressPercentage}%
</span>
</div>
)}
</div>
</a>
{course.slug && (
<div className="absolute right-2 top-2">
<AICourseActions courseSlug={course.slug} />
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,533 @@
import {
BookOpenCheck,
ChevronLeft,
CircleAlert,
CircleOff,
Loader2,
Menu,
Play,
X,
} from 'lucide-react';
import { useState } from 'react';
import { type AiCourse } from '../../lib/ai';
import { cn } from '../../lib/classname';
import { slugify } from '../../lib/slugger';
import { useIsPaidUser } from '../../queries/billing';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { CheckIcon } from '../ReactIcons/CheckIcon';
import { ErrorIcon } from '../ReactIcons/ErrorIcon';
import { AICourseLesson } from './AICourseLesson';
import { AICourseLimit } from './AICourseLimit';
import { AICourseSidebarModuleList } from './AICourseSidebarModuleList';
import { AILimitsPopup } from './AILimitsPopup';
import { RegenerateOutline } from './RegenerateOutline';
type AICourseContentProps = {
courseSlug?: string;
course: AiCourse;
isLoading: boolean;
error?: string;
onRegenerateOutline: (prompt?: string) => void;
};
export function AICourseContent(props: AICourseContentProps) {
const { course, courseSlug, isLoading, error, onRegenerateOutline } = props;
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
const [activeModuleIndex, setActiveModuleIndex] = useState(0);
const [activeLessonIndex, setActiveLessonIndex] = useState(0);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [viewMode, setViewMode] = useState<'module' | 'outline'>('outline');
const { isPaidUser } = useIsPaidUser();
const aiCourseProgress = course.done || [];
const [expandedModules, setExpandedModules] = useState<
Record<number, boolean>
>({});
const goToNextModule = () => {
if (activeModuleIndex >= course.modules.length) {
return;
}
const nextModuleIndex = activeModuleIndex + 1;
setActiveModuleIndex(nextModuleIndex);
setActiveLessonIndex(0);
setExpandedModules((prev) => {
const newState: Record<number, boolean> = {};
course.modules.forEach((_, idx) => {
newState[idx] = false;
});
newState[nextModuleIndex] = true;
return newState;
});
};
const goToNextLesson = () => {
const currentModule = course.modules[activeModuleIndex];
if (currentModule && activeLessonIndex < currentModule.lessons.length - 1) {
setActiveLessonIndex(activeLessonIndex + 1);
} else {
goToNextModule();
}
};
const goToPrevLesson = () => {
if (activeLessonIndex > 0) {
setActiveLessonIndex(activeLessonIndex - 1);
return;
}
const prevModule = course.modules[activeModuleIndex - 1];
if (!prevModule) {
return;
}
const prevModuleIndex = activeModuleIndex - 1;
setActiveModuleIndex(prevModuleIndex);
setActiveLessonIndex(prevModule.lessons.length - 1);
// Expand the previous module in the sidebar
setExpandedModules((prev) => {
const newState: Record<number, boolean> = {};
// Set all modules to collapsed
course.modules.forEach((_, idx) => {
newState[idx] = false;
});
// Expand only the previous module
newState[prevModuleIndex] = true;
return newState;
});
};
const currentModule = course.modules[activeModuleIndex];
const currentLesson = currentModule?.lessons[activeLessonIndex];
const totalModules = course.modules.length;
const totalLessons = currentModule?.lessons.length || 0;
const totalCourseLessons = course.modules.reduce(
(total, module) => total + module.lessons.length,
0,
);
const totalDoneLessons = (course?.done || []).length;
const finishedPercentage = Math.round(
(totalDoneLessons / totalCourseLessons) * 100,
);
const modals = (
<>
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
{showAILimitsPopup && (
<AILimitsPopup
onClose={() => setShowAILimitsPopup(false)}
onUpgrade={() => {
setShowAILimitsPopup(false);
setShowUpgradeModal(true);
}}
/>
)}
</>
);
if (error && !isLoading) {
const isLimitReached = error.includes('limit');
const isNotFound = error.includes('not exist');
let icon = <ErrorIcon additionalClasses="mb-4 size-16" />;
let title = 'Error occurred';
let message = error;
if (isLimitReached) {
icon = <CircleAlert className="mb-4 size-16 text-yellow-500" />;
title = 'Limit Reached';
message =
'You have reached the daily AI usage limit. Please upgrade your account to continue.';
} else if (isNotFound) {
icon = <CircleOff className="mb-4 size-16 text-gray-300" />;
title = 'Course Not Found';
message =
'The course you are looking for does not exist. Why not create your own course?';
}
const showUpgradeButton = isLimitReached && !isPaidUser;
return (
<>
{modals}
<div className="flex h-screen flex-col items-center justify-center px-4 text-center">
{icon}
<h1 className="mb-2 text-2xl font-bold">{title}</h1>
<p className="max-w-sm text-balance text-gray-500">{message}</p>
{showUpgradeButton && (
<div className="my-5">
<button
onClick={() => setShowUpgradeModal(true)}
className="rounded-md bg-yellow-400 px-6 py-2 text-sm font-medium text-black hover:bg-yellow-500"
>
Upgrade to remove Limits
</button>
<p className="mt-5 text-sm text-black">
<a
href="/ai-tutor"
className="font-medium underline underline-offset-2"
>
Back to AI Tutor
</a>
</p>
</div>
)}
{(isNotFound || !showUpgradeButton) && (
<div className="my-5">
<a
href="/ai-tutor"
className="rounded-md bg-black px-6 py-2 text-sm font-medium text-white hover:bg-opacity-80"
>
Create a course with AI
</a>
</div>
)}
</div>
</>
);
}
const isViewingLesson = viewMode === 'module';
return (
<section className="flex h-screen flex-grow flex-col overflow-hidden bg-gray-50">
{modals}
<div className="border-b border-gray-200 bg-gray-100">
<div className="flex items-center justify-between px-4 py-2">
<a
href="/ai-tutor"
onClick={(e) => {
if (isViewingLesson) {
e.preventDefault();
setViewMode('outline');
}
}}
className="flex flex-row items-center gap-1.5 text-sm font-medium text-gray-700 hover:text-gray-900"
aria-label="Back to generator"
>
<ChevronLeft className="size-4" strokeWidth={2.5} />
Back {isViewingLesson ? 'to Outline' : 'to AI Tutor'}
</a>
<div className="flex items-center gap-2">
<div className="flex flex-row lg:hidden">
<AICourseLimit
onUpgrade={() => setShowUpgradeModal(true)}
onShowLimits={() => setShowAILimitsPopup(true)}
/>
</div>
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="flex items-center justify-center text-gray-400 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-900 lg:hidden"
>
{sidebarOpen ? (
<X size={17} strokeWidth={3} />
) : (
<Menu size={17} strokeWidth={3} />
)}
</button>
</div>
</div>
</div>
<header className="flex items-center justify-between border-b border-gray-200 bg-white px-6 max-lg:py-4 lg:h-[80px]">
<div className="flex items-center">
<div className="flex flex-col">
<h1 className="text-balance text-xl font-bold !leading-tight text-gray-900 max-lg:mb-0.5 max-lg:text-lg">
{course.title || 'Loading Course...'}
</h1>
<div className="mt-1 flex flex-row items-center gap-2 text-sm text-gray-600 max-lg:text-xs">
<span className="font-medium">{totalModules} modules</span>
<span className="text-gray-400"></span>
<span className="font-medium">{totalCourseLessons} lessons</span>
{viewMode === 'module' && (
<span className="flex flex-row items-center gap-1 lg:hidden">
<span className="text-gray-400"></span>
<button
className="underline underline-offset-2"
onClick={() => {
setExpandedModules({});
setViewMode('outline');
}}
>
View outline
</button>
</span>
)}
{finishedPercentage > 0 && (
<>
<span className="text-gray-400"></span>
<span className="font-medium text-green-600">
{finishedPercentage}% complete
</span>
</>
)}
</div>
</div>
</div>
<div className="flex gap-2">
<div className="hidden gap-2 lg:flex">
<AICourseLimit
onUpgrade={() => setShowUpgradeModal(true)}
onShowLimits={() => setShowAILimitsPopup(true)}
/>
</div>
{viewMode === 'module' && (
<button
onClick={() => {
setExpandedModules({});
setViewMode('outline');
}}
className="flex flex-shrink-0 items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-900 max-lg:hidden"
>
<BookOpenCheck size={18} className="mr-2" />
View Course Outline
</button>
)}
</div>
</header>
<div className="flex flex-1 overflow-hidden">
<aside
className={cn(
'fixed inset-y-0 left-0 z-20 w-80 transform overflow-y-auto border-r border-gray-200 bg-white transition-transform duration-200 ease-in-out lg:relative lg:mt-0 lg:translate-x-0',
sidebarOpen ? 'translate-x-0' : '-translate-x-full',
)}
>
<div
className={cn(
'relative flex min-h-[40px] items-center justify-between border-b border-gray-200 px-3',
isLoading && 'striped-loader bg-gray-50',
)}
>
{!isLoading && (
<div className="flex w-full items-center justify-between text-xs text-black">
<span>
<span className="relative z-10 rounded-full bg-yellow-400 px-1.5 py-0.5">
{finishedPercentage}%
</span>{' '}
<span className="relative z-10">Completed</span>
</span>
<span
style={{
width: `${finishedPercentage}%`,
}}
className={cn(
'absolute bottom-0 left-0 top-0',
'bg-gray-200/50',
)}
></span>
{viewMode !== 'outline' && (
<button
onClick={() => {
setExpandedModules({});
setViewMode('outline');
}}
className="flex items-center gap-1 rounded-md bg-gray-200 px-2.5 py-1.5 text-xs transition-colors hover:bg-gray-300"
>
<BookOpenCheck size={14} />
View Outline
</button>
)}
{viewMode === 'outline' && (
<button
onClick={() => {
setExpandedModules({
...expandedModules,
0: true,
});
setActiveModuleIndex(0);
setActiveLessonIndex(0);
setViewMode('module');
}}
className="flex items-center gap-1 rounded-md bg-gray-200 px-2.5 py-1.5 text-xs transition-colors hover:bg-gray-300"
>
<Play size={14} />
Start Course
</button>
)}
</div>
)}
<button
onClick={() => setSidebarOpen(false)}
className="rounded-md p-1 hover:bg-gray-100 lg:hidden"
>
<X size={18} />
</button>
</div>
<AICourseSidebarModuleList
course={course}
courseSlug={courseSlug}
activeModuleIndex={
viewMode === 'module' ? activeModuleIndex : undefined
}
setActiveModuleIndex={setActiveModuleIndex}
activeLessonIndex={
viewMode === 'module' ? activeLessonIndex : undefined
}
setActiveLessonIndex={setActiveLessonIndex}
setSidebarOpen={setSidebarOpen}
viewMode={viewMode}
setViewMode={setViewMode}
expandedModules={expandedModules}
setExpandedModules={setExpandedModules}
isLoading={isLoading}
/>
</aside>
<main
className={cn(
'flex-1 overflow-y-auto p-6 transition-all duration-200 ease-in-out max-lg:p-3',
sidebarOpen ? 'lg:ml-0' : '',
)}
>
{viewMode === 'module' && (
<AICourseLesson
courseSlug={courseSlug!}
progress={aiCourseProgress}
activeModuleIndex={activeModuleIndex}
totalModules={totalModules}
currentModuleTitle={currentModule?.title || ''}
activeLessonIndex={activeLessonIndex}
totalLessons={totalLessons}
currentLessonTitle={currentLesson || ''}
onGoToPrevLesson={goToPrevLesson}
onGoToNextLesson={goToNextLesson}
key={`${courseSlug}-${activeModuleIndex}-${activeLessonIndex}`}
onUpgrade={() => setShowUpgradeModal(true)}
/>
)}
{viewMode === 'outline' && (
<div className="mx-auto rounded-xl border border-gray-200 bg-white shadow-sm lg:max-w-3xl">
<div
className={cn(
'relative mb-1 flex items-start justify-between border-b border-gray-100 p-6 max-lg:hidden',
isLoading && 'striped-loader',
)}
>
<div>
<h2 className="mb-1 text-balance text-2xl font-bold max-lg:text-lg max-lg:leading-tight">
{course.title || 'Loading course ..'}
</h2>
<p className="text-sm capitalize text-gray-500">
{course.title ? course.difficulty : 'Please wait ..'}
</p>
</div>
{!isLoading && (
<RegenerateOutline
onRegenerateOutline={onRegenerateOutline}
/>
)}
</div>
{course.title ? (
<div className="flex flex-col p-6 max-lg:mt-0.5 max-lg:p-4">
{course.modules.map((courseModule, moduleIdx) => {
return (
<div
key={moduleIdx}
className="mb-5 pb-4 last:border-0 last:pb-0 max-lg:mb-2"
>
<h2 className="mb-4 text-xl font-bold text-gray-800 max-lg:mb-2 max-lg:text-lg max-lg:leading-tight">
{courseModule.title}
</h2>
<div className="divide-y divide-gray-100">
{courseModule.lessons.map((lesson, lessonIdx) => {
const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`;
const isCompleted = aiCourseProgress.includes(key);
return (
<div
key={key}
className="flex cursor-pointer items-center gap-2 px-2 py-2.5 transition-colors hover:bg-gray-100 max-lg:px-0 max-lg:py-1.5"
onClick={() => {
setActiveModuleIndex(moduleIdx);
setActiveLessonIndex(lessonIdx);
setExpandedModules((prev) => {
const newState: Record<number, boolean> =
{};
course.modules.forEach((_, idx) => {
newState[idx] = false;
});
newState[moduleIdx] = true;
return newState;
});
setSidebarOpen(false);
setViewMode('module');
}}
>
{!isCompleted && (
<span
className={cn(
'flex size-6 flex-shrink-0 items-center justify-center rounded-full bg-gray-200 text-sm font-medium text-gray-800 max-lg:size-5 max-lg:text-xs',
)}
>
{lessonIdx + 1}
</span>
)}
{isCompleted && (
<CheckIcon additionalClasses="size-6 flex-shrink-0 text-green-500" />
)}
<p className="flex-1 truncate text-base text-gray-800 max-lg:text-sm">
{lesson.replace(/^Lesson\s*?\d+[\.:]\s*/, '')}
</p>
<span className="text-sm font-medium text-gray-700 max-lg:hidden">
{isCompleted ? 'View' : 'Start'}
</span>
</div>
);
})}
</div>
</div>
);
})}
</div>
) : (
<div className="flex h-64 items-center justify-center">
<Loader2 size={36} className="animate-spin text-gray-300" />
</div>
)}
</div>
)}
<div className="mb-10 mt-5 mx-auto text-center text-sm text-gray-400">
AI can make mistakes, check imporant info.
</div>
</main>
</div>
{sidebarOpen && (
<div
className="fixed inset-0 z-10 bg-gray-900 bg-opacity-50 lg:hidden"
onClick={() => setSidebarOpen(false)}
></div>
)}
</section>
);
}

View File

@@ -0,0 +1,131 @@
.prose ul li > code,
.prose ol li > code,
p code,
a > code,
strong > code,
em > code,
h1 > code,
h2 > code,
h3 > code {
background: #ebebeb !important;
color: currentColor !important;
font-size: 14px;
font-weight: normal !important;
}
.course-ai-content.course-content.prose ul li > code,
.course-ai-content.course-content.prose ol li > code,
.course-ai-content.course-content.prose p code,
.course-ai-content.course-content.prose a > code,
.course-ai-content.course-content.prose strong > code,
.course-ai-content.course-content.prose em > code,
.course-ai-content.course-content.prose h1 > code,
.course-ai-content.course-content.prose h2 > code,
.course-ai-content.course-content.prose h3 > code,
.course-notes-content.prose ul li > code,
.course-notes-content.prose ol li > code,
.course-notes-content.prose p code,
.course-notes-content.prose a > code,
.course-notes-content.prose strong > code,
.course-notes-content.prose em > code,
.course-notes-content.prose h1 > code,
.course-notes-content.prose h2 > code,
.course-notes-content.prose h3 > code {
font-size: 12px !important;
}
.course-ai-content pre {
-ms-overflow-style: none;
scrollbar-width: none;
}
.course-ai-content pre::-webkit-scrollbar {
display: none;
}
.course-ai-content pre,
.course-notes-content pre {
overflow: scroll;
font-size: 15px;
margin: 10px 0;
}
.prose ul li > code:before,
p > code:before,
.prose ul li > code:after,
.prose ol li > code:before,
p > code:before,
.prose ol li > code:after,
.course-content h1 > code:after,
.course-content h1 > code:before,
.course-content h2 > code:after,
.course-content h2 > code:before,
.course-content h3 > code:after,
.course-content h3 > code:before,
.course-content h4 > code:after,
.course-content h4 > code:before,
p > code:after,
a > code:after,
a > code:before {
content: '' !important;
}
.course-content.prose ul li > code,
.course-content.prose ol li > code,
.course-content p code,
.course-content a > code,
.course-content strong > code,
.course-content em > code,
.course-content h1 > code,
.course-content h2 > code,
.course-content h3 > code,
.course-content table code {
background: #f4f4f5 !important;
border: 1px solid #282a36 !important;
color: #282a36 !important;
padding: 2px 4px;
border-radius: 5px;
font-size: 16px !important;
white-space: pre;
font-weight: normal;
}
.course-content blockquote {
font-style: normal;
}
.course-content.prose blockquote h1,
.course-content.prose blockquote h2,
.course-content.prose blockquote h3,
.course-content.prose blockquote h4 {
font-style: normal;
margin-bottom: 8px;
}
.course-content.prose ul li > code:before,
.course-content p > code:before,
.course-content.prose ul li > code:after,
.course-content p > code:after,
.course-content h2 > code:after,
.course-content h2 > code:before,
.course-content table code:before,
.course-content table code:after,
.course-content a > code:after,
.course-content a > code:before,
.course-content h2 code:after,
.course-content h2 code:before,
.course-content h2 code:after,
.course-content h2 code:before {
content: '' !important;
}
.course-content table {
border-collapse: collapse;
border: 1px solid black;
border-radius: 5px;
}
.course-content table td,
.course-content table th {
padding: 5px 10px;
}

View File

@@ -0,0 +1,74 @@
import { ArrowRightIcon, BotIcon } from 'lucide-react';
import { useState } from 'react';
import {
AICourseFollowUpPopover,
type AIChatHistoryType,
} from './AICourseFollowUpPopover';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
type AICourseFollowUpProps = {
courseSlug: string;
moduleTitle: string;
lessonTitle: string;
};
export function AICourseFollowUp(props: AICourseFollowUpProps) {
const { courseSlug, moduleTitle, lessonTitle } = props;
const [isOpen, setIsOpen] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [courseAIChatHistory, setCourseAIChatHistory] = useState<
AIChatHistoryType[]
>([
{
role: 'assistant',
content:
'Hey, I am your AI instructor. Here are some examples of what you can ask me about 🤖',
isDefault: true,
},
]);
return (
<div className="relative">
<button
className="mt-4 flex w-full items-center gap-2 rounded-lg border border-yellow-300 bg-yellow-100 p-4 hover:bg-yellow-200 max-lg:mt-3 max-lg:text-sm"
onClick={() => setIsOpen(true)}
>
<BotIcon className="h-4 w-4" />
<span>Ask AI some follow up questions</span>
<ArrowRightIcon className="ml-auto h-4 w-4 max-sm:hidden" />
</button>
{showUpgradeModal && (
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
)}
{isOpen && (
<AICourseFollowUpPopover
courseSlug={courseSlug}
moduleTitle={moduleTitle}
lessonTitle={lessonTitle}
courseAIChatHistory={courseAIChatHistory}
setCourseAIChatHistory={setCourseAIChatHistory}
onUpgradeClick={() => {
setIsOpen(false);
setShowUpgradeModal(true);
}}
onOutsideClick={() => {
if (!isOpen) {
return;
}
setIsOpen(false);
}}
/>
)}
{isOpen && (
<div className="pointer-events-none fixed inset-0 z-50 bg-black/50" />
)}
</div>
);
}

View File

@@ -0,0 +1,390 @@
import { useQuery } from '@tanstack/react-query';
import {
BookOpen,
Bot,
Code,
Globe, Hammer,
HelpCircle,
LockIcon,
Send,
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import { flushSync } from 'react-dom';
import TextareaAutosize from 'react-textarea-autosize';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { useToast } from '../../hooks/use-toast';
import { readStream } from '../../lib/ai';
import { cn } from '../../lib/classname';
import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
import {
markdownToHtml,
markdownToHtmlWithHighlighting,
} from '../../lib/markdown';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
export type AllowedAIChatRole = 'user' | 'assistant';
export type AIChatHistoryType = {
role: AllowedAIChatRole;
content: string;
isDefault?: boolean;
html?: string;
};
type AICourseFollowUpPopoverProps = {
courseSlug: string;
moduleTitle: string;
lessonTitle: string;
courseAIChatHistory: AIChatHistoryType[];
setCourseAIChatHistory: (value: AIChatHistoryType[]) => void;
onOutsideClick?: () => void;
onUpgradeClick: () => void;
};
export function AICourseFollowUpPopover(props: AICourseFollowUpPopoverProps) {
const {
courseSlug,
moduleTitle,
lessonTitle,
onOutsideClick,
onUpgradeClick,
courseAIChatHistory,
setCourseAIChatHistory,
} = props;
const toast = useToast();
const containerRef = useRef<HTMLDivElement | null>(null);
const scrollareaRef = useRef<HTMLDivElement | null>(null);
const [isStreamingMessage, setIsStreamingMessage] = useState(false);
const [message, setMessage] = useState('');
const [streamedMessage, setStreamedMessage] = useState('');
useOutsideClick(containerRef, onOutsideClick);
const { data: tokenUsage, isLoading } = useQuery(
getAiCourseLimitOptions(),
queryClient,
);
const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
const handleChatSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const trimmedMessage = message.trim();
if (
!trimmedMessage ||
isStreamingMessage ||
!isLoggedIn() ||
isLimitExceeded ||
isLoading
) {
return;
}
const newMessages: AIChatHistoryType[] = [
...courseAIChatHistory,
{
role: 'user',
content: trimmedMessage,
},
];
flushSync(() => {
setCourseAIChatHistory(newMessages);
setMessage('');
});
scrollToBottom();
completeCourseAIChat(newMessages);
};
const scrollToBottom = () => {
scrollareaRef.current?.scrollTo({
top: scrollareaRef.current.scrollHeight,
behavior: 'smooth',
});
};
const completeCourseAIChat = async (messages: AIChatHistoryType[]) => {
setIsStreamingMessage(true);
const response = await fetch(
`${import.meta.env.PUBLIC_API_URL}/v1-follow-up-ai-course/${courseSlug}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
moduleTitle,
lessonTitle,
messages: messages.slice(-10),
}),
},
);
if (!response.ok) {
const data = await response.json();
toast.error(data?.message || 'Something went wrong');
setCourseAIChatHistory([...messages].slice(0, messages.length - 1));
setIsStreamingMessage(false);
if (data.status === 401) {
removeAuthToken();
window.location.reload();
}
}
const reader = response.body?.getReader();
if (!reader) {
setIsStreamingMessage(false);
toast.error('Something went wrong');
return;
}
await readStream(reader, {
onStream: async (content) => {
flushSync(() => {
setStreamedMessage(content);
});
scrollToBottom();
},
onStreamEnd: async (content) => {
const newMessages: AIChatHistoryType[] = [
...messages,
{
role: 'assistant',
content,
html: await markdownToHtmlWithHighlighting(content),
},
];
flushSync(() => {
setStreamedMessage('');
setIsStreamingMessage(false);
setCourseAIChatHistory(newMessages);
});
queryClient.invalidateQueries(getAiCourseLimitOptions());
scrollToBottom();
},
});
setIsStreamingMessage(false);
};
useEffect(() => {
scrollToBottom();
}, []);
return (
<div
className="absolute bottom-0 left-0 z-[99] flex h-[500px] w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow"
ref={containerRef}
>
<div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm">
<h4 className="text-base font-medium">Course AI</h4>
</div>
<div
className="scrollbar-thumb-gray-300 scrollbar-track-transparent scrollbar-thin relative grow overflow-y-auto"
ref={scrollareaRef}
>
<div className="absolute inset-0 flex flex-col">
<div className="flex grow flex-col justify-end">
<div className="flex flex-col justify-end gap-2 px-3 py-2">
{courseAIChatHistory.map((chat, index) => {
return (
<>
<AIChatCard
key={`chat-${index}`}
role={chat.role}
content={chat.content}
html={chat.html}
/>
{chat.isDefault && (
<div className="mb-1 mt-0.5">
<div className="grid grid-cols-2 gap-2">
{capabilities.map((capability, index) => (
<CapabilityCard
key={`capability-${index}`}
{...capability}
/>
))}
</div>
</div>
)}
</>
);
})}
{isStreamingMessage && !streamedMessage && (
<AIChatCard role="assistant" content="Thinking..." />
)}
{streamedMessage && (
<AIChatCard role="assistant" content={streamedMessage} />
)}
</div>
</div>
</div>
</div>
<form
className="relative flex items-start border-t border-gray-200 text-sm"
onSubmit={handleChatSubmit}
>
{isLimitExceeded && (
<div className="absolute inset-0 flex items-center justify-center gap-2 bg-black text-white">
<LockIcon className="size-4 cursor-not-allowed" strokeWidth={2.5} />
<p className="cursor-not-allowed">Limit reached for today</p>
<button
onClick={() => {
onUpgradeClick();
}}
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
>
Upgrade for more
</button>
</div>
)}
<TextareaAutosize
className="h-full min-h-[41px] grow resize-none bg-transparent px-4 py-2 focus:outline-none"
placeholder="Ask AI anything about the lesson..."
value={message}
onChange={(e) => setMessage(e.target.value)}
autoFocus={true}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
handleChatSubmit(e as unknown as FormEvent<HTMLFormElement>);
}
}}
/>
<button
type="submit"
disabled={isStreamingMessage || isLimitExceeded}
className="flex aspect-square size-[41px] items-center justify-center text-zinc-500 hover:text-black"
>
<Send className="size-4 stroke-[2.5]" />
</button>
</form>
</div>
);
}
type AIChatCardProps = {
role: AllowedAIChatRole;
content: string;
html?: string;
};
function AIChatCard(props: AIChatCardProps) {
const { role, content, html: defaultHtml } = props;
const html = useMemo(() => {
if (defaultHtml) {
return defaultHtml;
}
return markdownToHtml(content, false);
}, [content, defaultHtml]);
return (
<div
className={cn(
'flex flex-col rounded-lg',
role === 'user' ? 'bg-gray-300/30' : 'bg-yellow-500/30',
)}
>
<div className="flex items-start gap-2.5 p-3">
<div
className={cn(
'flex size-6 shrink-0 items-center justify-center rounded-full',
role === 'user'
? 'bg-gray-200 text-black'
: 'bg-yellow-400 text-black',
)}
>
<Bot className="size-4 stroke-[2.5]" />
</div>
<div
className="course-content course-ai-content prose prose-sm mt-0.5 max-w-full overflow-hidden text-sm"
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
</div>
);
}
type CapabilityCardProps = {
icon: React.ReactNode;
title: string;
description: string;
className?: string;
};
function CapabilityCard({
icon,
title,
description,
className,
}: CapabilityCardProps) {
return (
<div
className={cn(
'flex flex-col gap-2 rounded-lg bg-yellow-500/10 p-3',
className,
)}
>
<div className="flex items-center gap-2">
{icon}
<span className="text-[13px] font-medium leading-none text-black">
{title}
</span>
</div>
<p className="text-[12px] leading-normal text-gray-600">{description}</p>
</div>
);
}
const capabilities = [
{
icon: (
<HelpCircle
className="size-4 shrink-0 text-yellow-600"
strokeWidth={2.5}
/>
),
title: 'Clarify Concepts',
description: "If you don't understand a concept, ask me to clarify it",
},
{
icon: (
<BookOpen className="size-4 shrink-0 text-yellow-600" strokeWidth={2.5} />
),
title: 'More Details',
description: 'Get deeper insights about topics covered in the lesson',
},
{
icon: (
<Hammer className="size-4 shrink-0 text-yellow-600" strokeWidth={2.5} />
),
title: 'Real-world Examples',
description: 'Ask for real-world examples to understand better',
},
{
icon: <Bot className="size-4 shrink-0 text-yellow-600" strokeWidth={2.5} />,
title: 'Best Practices',
description: 'Learn about best practices and common pitfalls',
},
] as const;

View File

@@ -0,0 +1,387 @@
import { useMutation } from '@tanstack/react-query';
import {
CheckIcon,
ChevronLeft,
ChevronRight,
Loader2Icon,
LockIcon,
XIcon,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import type { AICourseDocument } from '../../api/ai-roadmap';
import { readStream } from '../../lib/ai';
import { cn } from '../../lib/classname';
import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
import {
markdownToHtml,
markdownToHtmlWithHighlighting,
} from '../../lib/markdown';
import { httpPatch } from '../../lib/query-http';
import { slugify } from '../../lib/slugger';
import {
getAiCourseLimitOptions,
getAiCourseOptions,
} from '../../queries/ai-course';
import { useIsPaidUser } from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
import { AICourseFollowUp } from './AICourseFollowUp';
import './AICourseFollowUp.css';
import { RegenerateLesson } from './RegenerateLesson';
type AICourseLessonProps = {
courseSlug: string;
progress: string[];
activeModuleIndex: number;
totalModules: number;
currentModuleTitle: string;
activeLessonIndex: number;
totalLessons: number;
currentLessonTitle: string;
onGoToPrevLesson: () => void;
onGoToNextLesson: () => void;
onUpgrade: () => void;
};
export function AICourseLesson(props: AICourseLessonProps) {
const {
courseSlug,
progress = [],
activeModuleIndex,
totalModules,
currentModuleTitle,
activeLessonIndex,
totalLessons,
currentLessonTitle,
onGoToPrevLesson,
onGoToNextLesson,
onUpgrade,
} = props;
const [isLoading, setIsLoading] = useState(true);
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState('');
const [lessonHtml, setLessonHtml] = useState('');
const lessonId = `${slugify(String(activeModuleIndex))}-${slugify(String(activeLessonIndex))}`;
const isLessonDone = progress?.includes(lessonId);
const { isPaidUser } = useIsPaidUser();
const abortController = useMemo(
() => new AbortController(),
[activeModuleIndex, activeLessonIndex],
);
const generateAiCourseContent = async (
isForce?: boolean,
customPrompt?: string,
) => {
setIsLoading(true);
setError('');
setLessonHtml('');
if (!isLoggedIn()) {
setIsLoading(false);
setError('Please login to generate course content');
return;
}
if (!currentModuleTitle || !currentLessonTitle) {
setIsLoading(false);
setError('Invalid module title or lesson title');
return;
}
const response = await fetch(
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course-lesson/${courseSlug}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal,
credentials: 'include',
body: JSON.stringify({
moduleIndex: activeModuleIndex,
lessonIndex: activeLessonIndex,
isForce,
customPrompt,
}),
},
);
if (!response.ok) {
const data = await response.json();
setError(data?.message || 'Something went wrong');
setIsLoading(false);
// Logout user if token is invalid
if (data.status === 401) {
removeAuthToken();
window.location.reload();
}
return;
}
if (!response.body) {
setIsLoading(false);
setError('No response body received');
return;
}
try {
const reader = response.body.getReader();
setIsLoading(false);
setIsGenerating(true);
await readStream(reader, {
onStream: async (result) => {
if (abortController.signal.aborted) {
return;
}
setLessonHtml(markdownToHtml(result, false));
},
onStreamEnd: async (result) => {
if (abortController.signal.aborted) {
return;
}
setLessonHtml(await markdownToHtmlWithHighlighting(result));
queryClient.invalidateQueries(getAiCourseLimitOptions());
setIsGenerating(false);
},
});
} catch (e) {
setError(e instanceof Error ? e.message : 'Something went wrong');
setIsLoading(false);
}
};
const { mutate: toggleDone, isPending: isTogglingDone } = useMutation(
{
mutationFn: () => {
return httpPatch<AICourseDocument>(
`/v1-toggle-done-ai-lesson/${courseSlug}`,
{
moduleIndex: activeModuleIndex,
lessonIndex: activeLessonIndex,
},
);
},
onSuccess: (data) => {
queryClient.setQueryData(
getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey,
data,
);
},
},
queryClient,
);
useEffect(() => {
generateAiCourseContent();
}, [currentModuleTitle, currentLessonTitle]);
useEffect(() => {
return () => {
abortController.abort();
};
}, [abortController]);
const cantGoForward =
(activeModuleIndex === totalModules - 1 &&
activeLessonIndex === totalLessons - 1) ||
isGenerating ||
isLoading;
const cantGoBack =
(activeModuleIndex === 0 && activeLessonIndex === 0) || isGenerating || isLoading;
return (
<div className="mx-auto max-w-4xl">
<div className="relative rounded-lg border border-gray-200 bg-white p-6 shadow-sm max-lg:px-4 max-lg:pb-4 max-lg:pt-3">
{(isGenerating || isLoading) && (
<div className="absolute right-3 top-3 flex items-center justify-center">
<Loader2Icon
size={18}
strokeWidth={3}
className="animate-spin text-gray-400/70"
/>
</div>
)}
<div className="mb-4 flex items-center justify-between">
<div className="text-sm text-gray-500">
Lesson {activeLessonIndex + 1} of {totalLessons}
</div>
{!isGenerating && !isLoading && (
<div className="absolute right-3 top-3 flex items-center justify-between gap-2">
<RegenerateLesson
onRegenerateLesson={(prompt) => {
generateAiCourseContent(true, prompt);
}}
/>
<button
disabled={isLoading || isTogglingDone}
className={cn(
'flex items-center gap-1.5 rounded-full bg-black py-1 pl-2 pr-3 text-sm text-white hover:bg-gray-800 disabled:opacity-50 max-lg:text-xs',
isLessonDone
? 'bg-red-500 hover:bg-red-600'
: 'bg-green-500 hover:bg-green-600',
)}
onClick={() => toggleDone()}
>
{isTogglingDone ? (
<>
<Loader2Icon
size={16}
strokeWidth={3}
className="animate-spin text-white"
/>
Please wait ...
</>
) : (
<>
{isLessonDone ? (
<>
<XIcon size={16} />
Mark as Undone
</>
) : (
<>
<CheckIcon size={16} />
Mark as Done
</>
)}
</>
)}
</button>
</div>
)}
</div>
<h1 className="mb-6 text-balance text-3xl font-semibold max-lg:mb-3 max-lg:text-xl">
{currentLessonTitle?.replace(/^Lesson\s*?\d+[\.:]\s*/, '')}
</h1>
{!error && isLoggedIn() && (
<div
className="course-content prose prose-lg mt-8 max-w-full text-black prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:mt-4 max-lg:text-base max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm"
dangerouslySetInnerHTML={{ __html: lessonHtml }}
/>
)}
{error && isLoggedIn() && (
<div className="mt-8 flex min-h-[300px] items-center justify-center rounded-xl bg-red-50/80">
{error.includes('reached the limit') ? (
<div className="flex max-w-sm flex-col items-center text-center">
<h2 className="text-xl font-semibold text-red-600">
Limit reached
</h2>
<p className="my-3 text-red-600">
You have reached the AI usage limit for today.
{!isPaidUser && <>Please upgrade your account to continue.</>}
{isPaidUser && <>Please wait until tomorrow to continue.</>}
</p>
{!isPaidUser && (
<button
onClick={() => {
onUpgrade();
}}
className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700"
>
Upgrade Account
</button>
)}
</div>
) : (
<p className="text-red-600">{error}</p>
)}
</div>
)}
{!isLoggedIn() && (
<div className="mt-8 flex min-h-[152px] flex-col items-center justify-center gap-3 rounded-lg border border-gray-200 p-8">
<LockIcon className="size-7 stroke-[2] text-gray-400/90" />
<p className="text-sm text-gray-500">
Please login to generate course content
</p>
</div>
)}
<div className="mt-8 flex items-center justify-between">
<button
onClick={onGoToPrevLesson}
disabled={cantGoBack}
className={cn(
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm',
cantGoBack
? 'cursor-not-allowed text-gray-400'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200',
)}
>
<ChevronLeft size={16} className="mr-2" />
Previous <span className="hidden lg:inline">&nbsp;Lesson</span>
</button>
<div>
<button
onClick={() => {
if (!isLessonDone) {
toggleDone(undefined, {
onSuccess: () => {
onGoToNextLesson();
},
});
} else {
onGoToNextLesson();
}
}}
disabled={cantGoForward || isTogglingDone}
className={cn(
'flex items-center rounded-full px-4 py-2 disabled:opacity-50 max-lg:px-3 max-lg:py-1.5 max-lg:text-sm',
cantGoForward
? 'cursor-not-allowed text-gray-400'
: 'bg-gray-800 text-white hover:bg-gray-700',
)}
>
{isTogglingDone ? (
<>
<Loader2Icon
size={16}
strokeWidth={3}
className="animate-spin text-white"
/>
Please wait ...
</>
) : (
<>
Next <span className="hidden lg:inline">&nbsp;Lesson</span>
<ChevronRight size={16} className="ml-2" />
</>
)}
</button>
</div>
</div>
</div>
{!isGenerating && !isLoading && (
<AICourseFollowUp
courseSlug={courseSlug}
moduleTitle={currentModuleTitle}
lessonTitle={currentLessonTitle}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,81 @@
import { useQuery } from '@tanstack/react-query';
import { Gift, Info } from 'lucide-react';
import { getPercentage } from '../../lib/number';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { billingDetailsOptions } from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
type AICourseLimitProps = {
onUpgrade: () => void;
onShowLimits: () => void;
};
export function AICourseLimit(props: AICourseLimitProps) {
const { onUpgrade, onShowLimits } = props;
const { data: limits, isLoading } = useQuery(
getAiCourseLimitOptions(),
queryClient,
);
const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
useQuery(billingDetailsOptions(), queryClient);
if (isLoading || !limits || isBillingDetailsLoading || !userBillingDetails) {
return (
<div className="hidden h-[38px] w-[208.09px] animate-pulse rounded-lg border border-gray-200 bg-gray-200 lg:block"></div>
);
}
const { used, limit } = limits;
const totalPercentage = getPercentage(used, limit);
// has consumed 85% of the limit
const isNearLimit = used >= limit * 0.85;
const isPaidUser = userBillingDetails.status === 'active';
return (
<>
{!isPaidUser ||
(isNearLimit && (
<button
className="mr-1 flex items-center gap-1 text-sm font-medium underline underline-offset-2 lg:hidden"
onClick={() => onShowLimits()}
>
<Info className="size-4" />
{totalPercentage}% limit used
</button>
))}
{(!isPaidUser || isNearLimit) && (
<button
onClick={() => {
onShowLimits();
}}
className="relative hidden h-full min-h-[38px] cursor-pointer items-center overflow-hidden rounded-lg border border-gray-300 px-3 py-1.5 text-sm hover:bg-gray-50 lg:flex"
>
<span className="relative z-10">
{totalPercentage}% of the daily limit used
</span>
<div
className="absolute inset-0 h-full bg-gray-200/80"
style={{
width: `${totalPercentage}%`,
}}
></div>
</button>
)}
{!isPaidUser && (
<button
className="hidden items-center justify-center gap-1 rounded-md bg-yellow-400 px-4 py-1 text-sm font-medium underline-offset-2 hover:bg-yellow-500 lg:flex"
onClick={() => onUpgrade()}
>
<Gift className="size-4" />
Upgrade
</button>
)}
</>
);
}

View File

@@ -0,0 +1,46 @@
import { SearchIcon } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useDebounceValue } from '../../hooks/use-debounce';
type AICourseSearchProps = {
value: string;
onChange: (value: string) => void;
};
export function AICourseSearch(props: AICourseSearchProps) {
const { value: defaultValue, onChange } = props;
const [searchTerm, setSearchTerm] = useState(defaultValue);
const debouncedSearchTerm = useDebounceValue(searchTerm, 500);
useEffect(() => {
setSearchTerm(defaultValue);
}, [defaultValue]);
useEffect(() => {
if (debouncedSearchTerm && debouncedSearchTerm.length < 3) {
return;
}
if (debouncedSearchTerm === defaultValue) {
return;
}
onChange(debouncedSearchTerm);
}, [debouncedSearchTerm]);
return (
<div className="relative w-64 max-sm:hidden">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<SearchIcon className="h-4 w-4 text-gray-400" />
</div>
<input
type="text"
className="block w-full rounded-md border border-gray-200 bg-white py-1.5 pl-10 pr-3 leading-5 placeholder-gray-500 focus:border-gray-300 focus:outline-none focus:ring-blue-500 disabled:opacity-70 sm:text-sm"
placeholder="Search your courses..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
);
}

View File

@@ -0,0 +1,204 @@
import { type Dispatch, type SetStateAction } from 'react';
import type { AiCourse } from '../../lib/ai';
import { Check, ChevronDownIcon, ChevronRightIcon } from 'lucide-react';
import { cn } from '../../lib/classname';
import { slugify } from '../../lib/slugger';
import { CheckIcon } from '../ReactIcons/CheckIcon';
import { CircularProgress } from './CircularProgress';
type AICourseModuleListProps = {
course: AiCourse;
courseSlug?: string;
activeModuleIndex: number | undefined;
setActiveModuleIndex: (index: number) => void;
activeLessonIndex: number | undefined;
setActiveLessonIndex: (index: number) => void;
setSidebarOpen: (open: boolean) => void;
viewMode: 'module' | 'outline';
setViewMode: (mode: 'module' | 'outline') => void;
expandedModules: Record<number, boolean>;
setExpandedModules: Dispatch<SetStateAction<Record<number, boolean>>>;
isLoading: boolean;
};
export function AICourseSidebarModuleList(props: AICourseModuleListProps) {
const {
course,
courseSlug,
activeModuleIndex,
setActiveModuleIndex,
activeLessonIndex,
setActiveLessonIndex,
setSidebarOpen,
setViewMode,
expandedModules,
setExpandedModules,
isLoading,
} = props;
const aiCourseProgress = course.done || [];
const toggleModule = (index: number) => {
setExpandedModules((prev) => {
// If this module is already expanded, collapse it
if (prev[index]) {
return {
...prev,
[index]: false,
};
}
// Otherwise, collapse all modules and expand only this one
const newState: Record<number, boolean> = {};
// Set all modules to collapsed
course.modules.forEach((_, idx) => {
newState[idx] = false;
});
// Expand only the clicked module
newState[index] = true;
return newState;
});
};
const done = aiCourseProgress || [];
return (
<nav className="bg-gray-100">
{course.modules.map((courseModule, moduleIdx) => {
const totalLessons = courseModule.lessons.length;
const completedLessons = courseModule.lessons.filter(
(lesson, lessonIdx) => {
const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`;
return done.includes(key);
},
).length;
const percentage = Math.round((completedLessons / totalLessons) * 100);
const isActive = expandedModules[moduleIdx];
const isModuleCompleted = completedLessons === totalLessons;
return (
<div key={moduleIdx} className="rounded-md">
<button
onClick={() => toggleModule(moduleIdx)}
className={cn(
'relative z-10 flex w-full cursor-pointer flex-row items-center gap-2 border-b border-b-gray-200 bg-white px-2 py-3 text-base text-gray-600 hover:bg-gray-100',
activeModuleIndex === moduleIdx
? 'text-gray-900'
: 'text-gray-700',
moduleIdx === 0 && 'pt-4',
)}
>
<div className="flex min-w-0 flex-1 items-center gap-2">
<div className="flex-shrink-0">
<CircularProgress
percentage={percentage}
isVisible={!isModuleCompleted}
isActive={isActive}
isLoading={isLoading}
>
<span
className={cn(
'flex size-[21px] flex-shrink-0 items-center justify-center rounded-full bg-gray-400/70 text-xs font-semibold text-white',
{
'bg-black': isActive,
'bg-green-600': isModuleCompleted,
},
)}
>
{!isModuleCompleted && moduleIdx + 1}
{isModuleCompleted && (
<Check className="size-3 stroke-[3] text-white" />
)}
</span>
</CircularProgress>
</div>
<span className="flex flex-1 items-center break-words text-left text-sm leading-relaxed">
{courseModule.title?.replace(/^Module\s*?\d+[\.:]\s*/, '')}
</span>
</div>
<div className="ml-auto self-center">
{expandedModules[moduleIdx] ? (
<ChevronDownIcon size={16} className="flex-shrink-0" />
) : (
<ChevronRightIcon size={16} className="flex-shrink-0" />
)}
</div>
</button>
{/* Lessons */}
{expandedModules[moduleIdx] && (
<div className="flex flex-col border-b border-b-gray-200 bg-gray-100">
{courseModule.lessons.map((lesson, lessonIdx) => {
const key = `${slugify(String(moduleIdx))}-${slugify(String(lessonIdx))}`;
const isCompleted = done.includes(key);
return (
<button
key={key}
onClick={() => {
setActiveModuleIndex(moduleIdx);
setActiveLessonIndex(lessonIdx);
setExpandedModules((prev) => {
const newState: Record<number, boolean> = {};
course.modules.forEach((_, idx) => {
newState[idx] = false;
});
newState[moduleIdx] = true;
return newState;
});
setSidebarOpen(false);
setViewMode('module');
}}
className={cn(
'flex w-full cursor-pointer items-center gap-2.5 py-3 pl-3.5 pr-2 text-left text-sm leading-normal',
activeModuleIndex === moduleIdx &&
activeLessonIndex === lessonIdx
? 'bg-gray-200 text-black'
: 'text-gray-600 hover:bg-gray-200/70',
)}
>
{isCompleted ? (
<CheckIcon
additionalClasses={cn(
'size-[18px] relative bg-white rounded-full top-[2px] flex-shrink-0 text-green-600',
{
'text-black':
activeModuleIndex === moduleIdx &&
activeLessonIndex === lessonIdx,
},
)}
/>
) : (
<span
className={cn(
'flex size-[18px] flex-shrink-0 items-center justify-center rounded-full bg-gray-400/70 text-xs font-semibold text-white',
{
'bg-black':
activeModuleIndex === moduleIdx &&
activeLessonIndex === lessonIdx,
},
)}
>
{lessonIdx + 1}
</span>
)}
<span className="break-words">
{lesson?.replace(/^Lesson\s*?\d+[\.:]\s*/, '')}
</span>
</button>
);
})}
</div>
)}
</div>
);
})}
</nav>
);
}

View File

@@ -0,0 +1,103 @@
import { Gift } from 'lucide-react';
import { Modal } from '../Modal';
import { formatCommaNumber } from '../../lib/number';
import { billingDetailsOptions } from '../../queries/billing';
import { queryClient } from '../../stores/query-client';
import { useQuery } from '@tanstack/react-query';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
type AILimitsPopupProps = {
onClose: () => void;
onUpgrade: () => void;
};
export function AILimitsPopup(props: AILimitsPopupProps) {
const { onClose, onUpgrade } = props;
const { data: limits, isLoading } = useQuery(
getAiCourseLimitOptions(),
queryClient,
);
const { used, limit } = limits ?? { used: 0, limit: 0 };
const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
useQuery(billingDetailsOptions(), queryClient);
const isPaidUser = userBillingDetails?.status === 'active';
return (
<Modal
onClose={onClose}
wrapperClassName="rounded-xl max-w-xl w-full h-auto"
bodyClassName="p-6"
overlayClassName="items-start md:items-center"
>
<h2 className="mb-8 text-center text-xl font-semibold">
Daily AI Limits
</h2>
{/* Usage Progress Bar */}
<div className="mb-6">
<div className="mb-2 flex justify-between">
<span className="text-sm font-medium">
Usage: {formatCommaNumber(used)}&nbsp;/&nbsp;
{formatCommaNumber(limit)} tokens
</span>
<span className="text-sm font-medium">
{Math.round((used / limit) * 100)}%
</span>
</div>
<div className="h-2.5 w-full rounded-full bg-gray-200">
<div
className="h-2.5 rounded-full bg-yellow-500"
style={{ width: `${Math.min(100, (used / limit) * 100)}%` }}
></div>
</div>
</div>
{/* Usage Stats */}
<div className="rounded-lg bg-gray-50 p-4">
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Used Today</p>
<p className="text-2xl font-bold">{formatCommaNumber(used)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Daily Limit</p>
<p className="text-2xl font-bold">{formatCommaNumber(limit)}</p>
</div>
</div>
</div>
{/* Explanation */}
<div className="mt-2">
<div className="space-y-3 text-gray-600">
<p className="text-sm">
Limit resets every 24 hours.{' '}
{!isPaidUser && 'Consider upgrading for more tokens.'}
</p>
</div>
</div>
{/* Action Button */}
<div className="mt-auto flex flex-col gap-2 pt-4">
{!isPaidUser && (
<button
onClick={onUpgrade}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-yellow-400 px-4 py-2.5 text-sm font-medium text-black transition-colors hover:bg-yellow-500"
>
<Gift className="size-4" />
Upgrade to Unlimited
</button>
)}
<button
onClick={onClose}
className="w-full rounded-lg bg-gray-200 px-4 py-2.5 text-sm text-gray-600 transition-colors hover:bg-gray-300"
>
Close
</button>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,57 @@
import { cn } from '../../lib/classname';
export function ChapterNumberSkeleton() {
return (
<div className="h-[28px] w-[28px] animate-pulse rounded-full bg-gray-200" />
);
}
type CircularProgressProps = {
percentage: number;
children: React.ReactNode;
isVisible?: boolean;
isActive?: boolean;
isLoading?: boolean;
};
export function CircularProgress(props: CircularProgressProps) {
const {
percentage,
children,
isVisible = true,
isActive = false,
isLoading = false,
} = props;
const circumference = 2 * Math.PI * 13;
const strokeDasharray = `${circumference}`;
const strokeDashoffset = circumference - (percentage / 100) * circumference;
return (
<div className="relative flex h-[28px] w-[28px] flex-shrink-0 items-center justify-center">
{isVisible && !isLoading && (
<svg className="absolute h-full w-full -rotate-90">
<circle
cx="14"
cy="14"
r="13"
stroke="currentColor"
strokeWidth="1.75"
fill="none"
className={cn('text-gray-400/70', {
'text-black': isActive,
})}
style={{
strokeDasharray,
strokeDashoffset,
transition: 'stroke-dashoffset 0.3s ease',
}}
/>
</svg>
)}
{!isLoading && children}
{isLoading && <ChapterNumberSkeleton />}
</div>
);
}

View File

@@ -0,0 +1,103 @@
import { useState } from 'react';
import { cn } from '../../lib/classname';
type QuestionProps = {
label: string;
placeholder: string;
autoFocus?: boolean;
value: string;
onChange: (value: string) => void;
};
function Question(props: QuestionProps) {
const { label, placeholder, value, onChange, autoFocus = false } = props;
return (
<div className="flex flex-col">
<label className="border-y bg-gray-100 px-4 py-2.5 text-sm font-medium text-gray-700">
{label}
</label>
<textarea
placeholder={placeholder}
className="min-h-[80px] w-full resize-none px-4 py-3 text-gray-700 placeholder:text-gray-400 focus:outline-none"
value={value}
onChange={(e) => onChange(e.target.value)}
autoFocus={autoFocus}
/>
</div>
);
}
type FineTuneCourseProps = {
hasFineTuneData: boolean;
about: string;
goal: string;
customInstructions: string;
setHasFineTuneData: (hasMetadata: boolean) => void;
setAbout: (about: string) => void;
setGoal: (goal: string) => void;
setCustomInstructions: (customInstructions: string) => void;
};
export function FineTuneCourse(props: FineTuneCourseProps) {
const {
about,
goal,
customInstructions,
hasFineTuneData,
setAbout,
setGoal,
setCustomInstructions,
setHasFineTuneData,
} = props;
return (
<div className="flex flex-col overflow-hidden rounded-lg border border-gray-200 transition-all">
<label
className={cn(
'group flex cursor-pointer select-none flex-row items-center gap-2.5 px-4 py-3 text-left text-gray-500 transition-colors hover:bg-gray-100 focus:outline-none',
hasFineTuneData && 'bg-gray-100',
)}
>
<input
id="fine-tune-checkbox"
type="checkbox"
className="h-4 w-4 group-hover:fill-current"
checked={hasFineTuneData}
onChange={() => {
setHasFineTuneData(!hasFineTuneData);
}}
/>
Tell us more to tailor the course (optional){' '}
<span className="ml-auto rounded-md bg-gray-400 px-2 py-0.5 text-xs text-white">
recommended
</span>
</label>
{hasFineTuneData && (
<div className="mt-0 flex flex-col">
<Question
label="Tell us about your self"
placeholder="e.g. I am a frontend developer and have good knowledge of HTML, CSS, and JavaScript."
value={about}
onChange={setAbout}
autoFocus={true}
/>
<Question
label="What is your goal with this course?"
placeholder="e.g. I want to be able to build Node.js APIs with Express.js and MongoDB."
value={goal}
onChange={setGoal}
/>
<Question
label="Custom Instructions (Optional)"
placeholder="Give additional instructions to the AI as if you were giving them to a friend."
value={customInstructions}
onChange={setCustomInstructions}
/>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,166 @@
import { useEffect, useState } from 'react';
import { getUrlParams } from '../../lib/browser';
import { isLoggedIn } from '../../lib/jwt';
import { getCourseFineTuneData, type AiCourse } from '../../lib/ai';
import { AICourseContent } from './AICourseContent';
import { generateCourse } from '../../helper/generate-ai-course';
import { useQuery } from '@tanstack/react-query';
import { getAiCourseOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
type GenerateAICourseProps = {};
export function GenerateAICourse(props: GenerateAICourseProps) {
const [term, setTerm] = useState('');
const [difficulty, setDifficulty] = useState('');
const [sessionId, setSessionId] = useState('');
const [goal, setGoal] = useState('');
const [about, setAbout] = useState('');
const [customInstructions, setCustomInstructions] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState('');
const [courseId, setCourseId] = useState('');
const [courseSlug, setCourseSlug] = useState('');
const [course, setCourse] = useState<AiCourse>({
title: '',
modules: [],
difficulty: '',
done: [],
});
// Once the course is generated, we fetch the course from the database
// so that we get the up-to-date course data and also so that we
// can reload the changes (e.g. progress) etc using queryClient.setQueryData
const { data: aiCourse } = useQuery(
getAiCourseOptions({ aiCourseSlug: courseSlug }),
queryClient,
);
useEffect(() => {
if (aiCourse) {
setCourse(aiCourse);
}
}, [aiCourse]);
useEffect(() => {
if (term || difficulty) {
return;
}
const params = getUrlParams();
const paramsTerm = params?.term;
const paramsDifficulty = params?.difficulty;
if (!paramsTerm || !paramsDifficulty) {
return;
}
setTerm(paramsTerm);
setDifficulty(paramsDifficulty);
const sessionId = params?.id;
setSessionId(sessionId);
let paramsGoal = '';
let paramsAbout = '';
let paramsCustomInstructions = '';
if (sessionId) {
const fineTuneData = getCourseFineTuneData(sessionId);
if (fineTuneData) {
paramsGoal = fineTuneData.goal;
paramsAbout = fineTuneData.about;
paramsCustomInstructions = fineTuneData.customInstructions;
setGoal(paramsGoal);
setAbout(paramsAbout);
setCustomInstructions(paramsCustomInstructions);
}
}
handleGenerateCourse({
term: paramsTerm,
difficulty: paramsDifficulty,
instructions: paramsCustomInstructions,
goal: paramsGoal,
about: paramsAbout,
});
}, [term, difficulty]);
const handleGenerateCourse = async (options: {
term: string;
difficulty: string;
instructions?: string;
goal?: string;
about?: string;
isForce?: boolean;
prompt?: string;
}) => {
const { term, difficulty, isForce, prompt, instructions, goal, about } =
options;
if (!isLoggedIn()) {
window.location.href = '/ai-tutor';
return;
}
await generateCourse({
term,
difficulty,
slug: courseSlug,
onCourseIdChange: setCourseId,
onCourseSlugChange: setCourseSlug,
onCourseChange: setCourse,
onLoadingChange: setIsLoading,
onError: setError,
instructions,
goal,
about,
isForce,
prompt,
});
};
useEffect(() => {
const handlePopState = (e: PopStateEvent) => {
const { courseId, courseSlug, term, difficulty } = e.state || {};
if (!courseId || !courseSlug) {
window.location.reload();
return;
}
setCourseId(courseId);
setCourseSlug(courseSlug);
setTerm(term);
setDifficulty(difficulty);
setIsLoading(true);
handleGenerateCourse({ term, difficulty }).finally(() => {
setIsLoading(false);
});
};
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
return (
<AICourseContent
courseSlug={courseSlug}
course={course}
isLoading={isLoading}
error={error}
onRegenerateOutline={(prompt) => {
handleGenerateCourse({
term,
difficulty,
isForce: true,
prompt,
});
}}
/>
);
}

View File

@@ -0,0 +1,107 @@
import { useQuery } from '@tanstack/react-query';
import { getAiCourseOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { useEffect, useState } from 'react';
import { AICourseContent } from './AICourseContent';
import { isLoggedIn } from '../../lib/jwt';
import { generateCourse } from '../../helper/generate-ai-course';
type GetAICourseProps = {
courseSlug: string;
};
export function GetAICourse(props: GetAICourseProps) {
const { courseSlug } = props;
const [isLoading, setIsLoading] = useState(true);
const [isRegenerating, setIsRegenerating] = useState(false);
const [error, setError] = useState('');
const { data: aiCourse, error: queryError } = useQuery(
{
...getAiCourseOptions({ aiCourseSlug: courseSlug }),
enabled: !!courseSlug && !!isLoggedIn(),
},
queryClient,
);
useEffect(() => {
if (!isLoggedIn()) {
window.location.href = '/ai-tutor';
}
}, [isLoggedIn]);
useEffect(() => {
if (!aiCourse) {
return;
}
setIsLoading(false);
}, [aiCourse]);
useEffect(() => {
if (!queryError) {
return;
}
setIsLoading(false);
setError(queryError.message);
}, [queryError]);
const handleRegenerateCourse = async (prompt?: string) => {
if (!aiCourse) {
return;
}
queryClient.setQueryData(
getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey,
{
...aiCourse,
title: '',
difficulty: '',
modules: [],
},
);
await generateCourse({
term: aiCourse.keyword,
difficulty: aiCourse.difficulty,
slug: courseSlug,
prompt,
onCourseChange: (course, rawData) => {
queryClient.setQueryData(
getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey,
{
...aiCourse,
title: course.title,
difficulty: course.difficulty,
modules: course.modules,
},
);
},
onLoadingChange: (isNewLoading) => {
setIsRegenerating(isNewLoading);
if (!isNewLoading) {
// TODO: Update progress
}
},
onError: setError,
isForce: true,
});
};
return (
<AICourseContent
course={{
title: aiCourse?.title || '',
modules: aiCourse?.modules || [],
difficulty: aiCourse?.difficulty || 'Easy',
done: aiCourse?.done || [],
}}
isLoading={isLoading || isRegenerating}
courseSlug={courseSlug}
error={error}
onRegenerateOutline={handleRegenerateCourse}
/>
);
}

View File

@@ -0,0 +1,69 @@
import { useState } from 'react';
import { Modal } from '../Modal';
export type ModifyCoursePromptProps = {
onClose: () => void;
onSubmit: (prompt: string) => void;
};
export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
const { onClose, onSubmit } = props;
const [prompt, setPrompt] = useState('');
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(prompt);
};
return (
<Modal
onClose={onClose}
wrapperClassName="rounded-xl max-w-xl w-full h-auto"
bodyClassName="p-6"
overlayClassName="items-start md:items-center"
>
<div className="flex flex-col gap-4">
<div>
<h2 className="mb-2 text-left text-xl font-semibold">
Give AI more context
</h2>
<p className="text-sm text-gray-500">
Pass additional information to the AI to generate a course outline.
</p>
</div>
<form className="flex flex-col gap-2" onSubmit={handleSubmit}>
<textarea
id="prompt"
autoFocus
rows={3}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className="w-full rounded-md border border-gray-200 p-2 placeholder:text-sm focus:outline-black"
placeholder="e.g. make sure to add a section on React hooks"
/>
<p className="text-sm text-gray-500">
Complete the sentence: "I want AI to..."
</p>
<div className="flex justify-end gap-2">
<button
className="rounded-md bg-gray-200 px-4 py-2.5 text-sm text-black hover:opacity-80"
onClick={onClose}
>
Cancel
</button>
<button
type="submit"
disabled={!prompt.trim()}
className="rounded-md bg-black px-4 py-2.5 text-sm text-white hover:opacity-80 disabled:opacity-50"
>
Modify Prompt
</button>
</div>
</form>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,88 @@
import { PenSquare, RefreshCcw } from 'lucide-react';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { cn } from '../../lib/classname';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
type RegenerateLessonProps = {
onRegenerateLesson: (prompt?: string) => void;
};
export function RegenerateLesson(props: RegenerateLessonProps) {
const { onRegenerateLesson } = props;
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showPromptModal, setShowPromptModal] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick(ref, () => setIsDropdownVisible(false));
return (
<>
{showUpgradeModal && (
<UpgradeAccountModal
onClose={() => {
setShowUpgradeModal(false);
}}
/>
)}
{showPromptModal && (
<ModifyCoursePrompt
title="Give AI more context"
description="Pass additional information to the AI to generate a lesson."
onClose={() => setShowPromptModal(false)}
onSubmit={(prompt) => {
setShowPromptModal(false);
onRegenerateLesson(prompt);
}}
/>
)}
<div className="relative mr-2 flex items-center" ref={ref}>
<button
className={cn('rounded-full p-1 text-gray-400 hover:text-black', {
'text-black': isDropdownVisible,
})}
onClick={() => setIsDropdownVisible(!isDropdownVisible)}
>
<PenSquare className="text-current" size={16} strokeWidth={2.5} />
</button>
{isDropdownVisible && (
<div className="absolute right-0 top-full min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
<button
onClick={() => {
onRegenerateLesson();
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
>
<RefreshCcw
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Regenerate
</button>
<button
onClick={() => {
setIsDropdownVisible(false);
setShowPromptModal(true);
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
>
<PenSquare
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Modify Prompt
</button>
</div>
)}
</div>
</>
);
}

View File

@@ -0,0 +1,86 @@
import { PenSquare, RefreshCcw } from 'lucide-react';
import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { cn } from '../../lib/classname';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
type RegenerateOutlineProps = {
onRegenerateOutline: (prompt?: string) => void;
};
export function RegenerateOutline(props: RegenerateOutlineProps) {
const { onRegenerateOutline } = props;
const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
const [showPromptModal, setShowPromptModal] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useOutsideClick(ref, () => setIsDropdownVisible(false));
return (
<>
{showUpgradeModal && (
<UpgradeAccountModal
onClose={() => {
setShowUpgradeModal(false);
}}
/>
)}
{showPromptModal && (
<ModifyCoursePrompt
onClose={() => setShowPromptModal(false)}
onSubmit={(prompt) => {
setShowPromptModal(false);
onRegenerateOutline(prompt);
}}
/>
)}
<div className="absolute right-3 top-3" ref={ref}>
<button
className={cn('text-gray-400 hover:text-black', {
'text-black': isDropdownVisible,
})}
onClick={() => setIsDropdownVisible(!isDropdownVisible)}
>
<PenSquare className="text-current" size={16} strokeWidth={2.5} />
</button>
{isDropdownVisible && (
<div className="absolute right-0 top-full min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
<button
onClick={() => {
onRegenerateOutline();
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
>
<RefreshCcw
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Regenerate
</button>
<button
onClick={() => {
setIsDropdownVisible(false);
setShowPromptModal(true);
}}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
>
<PenSquare
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Modify Prompt
</button>
</div>
)}
</div>
</>
);
}

View File

@@ -0,0 +1,195 @@
import { useQuery } from '@tanstack/react-query';
import {
getAiCourseLimitOptions,
listUserAiCoursesOptions,
type ListUserAiCoursesQuery,
} from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { AICourseCard } from './AICourseCard';
import { useEffect, useState } from 'react';
import { Gift, Loader2, User2 } from 'lucide-react';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
import { cn } from '../../lib/classname';
import { useIsPaidUser } from '../../queries/billing';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
import { AICourseSearch } from './AICourseSearch';
import { Pagination } from '../Pagination/Pagination';
type UserCoursesListProps = {};
export function UserCoursesList(props: UserCoursesListProps) {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [showUpgradePopup, setShowUpgradePopup] = useState(false);
const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
perPage: '10',
currPage: '1',
query: '',
});
const { data: limits, isLoading: isLimitsLoading } = useQuery(
getAiCourseLimitOptions(),
queryClient,
);
const { used, limit } = limits ?? { used: 0, limit: 0 };
const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
listUserAiCoursesOptions(pageState),
queryClient,
);
useEffect(() => {
setIsInitialLoading(false);
}, [userAiCourses]);
const courses = userAiCourses?.data ?? [];
const isAuthenticated = isLoggedIn();
const limitUsedPercentage = Math.round((used / limit) * 100);
useEffect(() => {
const queryParams = getUrlParams();
setPageState({
...pageState,
currPage: queryParams?.p || '1',
query: queryParams?.q || '',
});
}, []);
useEffect(() => {
if (pageState?.currPage !== '1' || pageState?.query !== '') {
setUrlParams({
p: pageState?.currPage || '1',
q: pageState?.query || '',
});
} else {
deleteUrlParam('p');
deleteUrlParam('q');
}
}, [pageState]);
return (
<>
{showUpgradePopup && (
<UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
)}
<div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold">
<span className="max-md:hidden">Your </span>Courses
</h2>
</div>
<div className="flex items-center gap-2">
{used > 0 && limit > 0 && !isPaidUserLoading && (
<div
className={cn(
'pointer-events-none flex items-center gap-2 opacity-0 transition-opacity',
{
'pointer-events-auto opacity-100': !isPaidUser,
},
)}
>
<p className="flex items-center text-sm text-yellow-600">
<span className="max-md:hidden">
{limitUsedPercentage}% of daily limit used{' '}
</span>
<span className="inline md:hidden">
{limitUsedPercentage}% used
</span>
<button
onClick={() => {
setShowUpgradePopup(true);
}}
className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pl-1.5 pr-2 text-xs text-white"
>
<Gift className="size-4" />
Upgrade
</button>
</p>
</div>
)}
<AICourseSearch
value={pageState?.query || ''}
onChange={(value) => {
setPageState({
...pageState,
query: value,
currPage: '1',
});
}}
/>
</div>
</div>
{!isInitialLoading && !isUserAiCoursesLoading && !isAuthenticated && (
<div className="flex min-h-[152px] flex-col items-center justify-center rounded-lg border border-gray-200 bg-white px-6 py-4">
<User2 className="mb-2 size-8 text-gray-300" />
<p className="max-w-sm text-balance text-center text-gray-500">
<button
onClick={() => {
showLoginPopup();
}}
className="font-medium text-black underline underline-offset-2 hover:opacity-80"
>
Sign up (free and takes 2s) or login
</button>{' '}
to generate and save courses.
</p>
</div>
)}
{!isUserAiCoursesLoading && !isInitialLoading && courses.length === 0 && isAuthenticated && (
<div className="flex min-h-[152px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
<p className="text-sm text-gray-600">
You haven't generated any courses yet.
</p>
</div>
)}
{(isUserAiCoursesLoading || isInitialLoading) && (
<div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
<Loader2
className="size-4 animate-spin text-gray-400"
strokeWidth={2.5}
/>
<p className="text-sm font-medium text-gray-600">Loading...</p>
</div>
)}
{!isUserAiCoursesLoading && courses && courses.length > 0 && (
<div className="flex flex-col gap-2">
{courses.map((course) => (
<AICourseCard key={course._id} course={course} />
))}
<Pagination
totalCount={userAiCourses?.totalCount || 0}
totalPages={userAiCourses?.totalPages || 0}
currPage={Number(userAiCourses?.currPage || 1)}
perPage={Number(userAiCourses?.perPage || 10)}
onPageChange={(page) => {
setPageState({ ...pageState, currPage: String(page) });
}}
className="rounded-lg border border-gray-200 bg-white p-4"
/>
</div>
)}
{!isUserAiCoursesLoading &&
(userAiCourses?.data?.length || 0 > 0) &&
courses.length === 0 && (
<div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
<p className="text-sm text-gray-600">
No courses match your search.
</p>
</div>
)}
</>
);
}

View File

@@ -128,32 +128,32 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) {
return allRoadmaps;
};
useEffect(() => {
if (debouncedSearchValue.length === 0 || isFirstRender.current) {
setSearchResults([]);
return;
}
// useEffect(() => {
// if (debouncedSearchValue.length === 0 || isFirstRender.current) {
// setSearchResults([]);
// return;
// }
setIsActive(true);
setIsLoading(true);
loadTopAIRoadmapTerm()
.then((results) => {
const normalizedSearchText = debouncedSearchValue.trim().toLowerCase();
const matchingOfficialRoadmaps = officialRoadmaps.filter((roadmap) => {
return (
roadmap.title.toLowerCase().indexOf(normalizedSearchText) !== -1
);
});
// setIsActive(true);
// setIsLoading(true);
// loadTopAIRoadmapTerm()
// .then((results) => {
// const normalizedSearchText = debouncedSearchValue.trim().toLowerCase();
// const matchingOfficialRoadmaps = officialRoadmaps.filter((roadmap) => {
// return (
// roadmap.title.toLowerCase().indexOf(normalizedSearchText) !== -1
// );
// });
setSearchResults(
[...matchingOfficialRoadmaps, ...results]?.slice(0, 5) || [],
);
setActiveCounter(0);
})
.finally(() => {
setIsLoading(false);
});
}, [debouncedSearchValue]);
// setSearchResults(
// [...matchingOfficialRoadmaps, ...results]?.slice(0, 5) || [],
// );
// setActiveCounter(0);
// })
// .finally(() => {
// setIsLoading(false);
// });
// }, [debouncedSearchValue]);
useEffect(() => {
if (isFirstRender.current) {

View File

@@ -11,7 +11,6 @@ import { useToast } from '../../hooks/use-toast';
import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { replaceChildren } from '../../lib/dom';
import { readAIRoadmapStream } from '../../helper/read-stream';
import {
getOpenAIKey,
isLoggedIn,
@@ -31,7 +30,7 @@ import { showLoginPopup } from '../../lib/popup.ts';
import { cn } from '../../lib/classname.ts';
import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts';
import { IS_KEY_ONLY_ROADMAP_GENERATION, readAIRoadmapStream } from '../../lib/ai.ts';
import { AITermSuggestionInput } from './AITermSuggestionInput.tsx';
import { IncreaseRoadmapLimit } from './IncreaseRoadmapLimit.tsx';
import { AuthenticationForm } from '../AuthenticationFlow/AuthenticationForm.tsx';

View File

@@ -3,13 +3,13 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { markdownToHtml } from '../../lib/markdown';
import { Ban, Cog, Contact, FileText, User, UserRound, X } from 'lucide-react';
import { Ban, Cog, Contact, FileText, X } from 'lucide-react';
import { Spinner } from '../ReactIcons/Spinner';
import type { RoadmapNodeDetails } from './GenerateRoadmap';
import { getOpenAIKey, isLoggedIn, removeAuthToken } from '../../lib/jwt';
import { readAIRoadmapContentStream } from '../../helper/read-stream';
import { cn } from '../../lib/classname';
import { showLoginPopup } from '../../lib/popup';
import { readAIRoadmapContentStream } from '../../lib/ai';
type RoadmapTopicDetailProps = RoadmapNodeDetails & {
onClose?: () => void;

View File

@@ -1,6 +1,6 @@
import { ChevronDown } from 'lucide-react';
import { useState } from 'react';
import { cn } from '../../lib/classname';
import { ChevronDown } from 'lucide-react';
type RelatedGuidesProps = {
relatedTitle?: string;
@@ -27,7 +27,7 @@ export function RelatedGuides(props: RelatedGuidesProps) {
<div className={cn('relative min-w-[250px] pt-0 lg:px-5 lg:pt-10')}>
<h4 className="text-lg font-medium max-lg:hidden">{relatedTitle}</h4>
<button
className="flex border-b w-full items-center justify-between gap-2 bg-gray-300 px-3 py-2 text-sm font-medium lg:hidden"
className="flex w-full items-center justify-between gap-2 border-b bg-gray-300 px-3 py-2 text-sm font-medium lg:hidden"
onClick={() => setIsOpen(!isOpen)}
>
{relatedTitle}

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