From 83106711239e03e7263995636550da730666b2f7 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Sat, 30 Sep 2023 18:55:24 +0600 Subject: [PATCH] Allow creating custom roadmaps (#4486) * wip: custom roadmap renderer * wip: custom roadmap events * wip: roadmap content * wip: svg styles * wip: custom roadmap progress * Render progress * Shortcut progress * Progress Tracking styles * wip: edit and share button * fix: disabled the share button * wip: content links rendering * Fix progress share * Replace disabled with `canShare` * wip: show custom roadmaps * wip: users all roadmaps * fix: create roadmap api * chore: roadmap sidebar icon * wip: content links * Update links color * Create roadmap home * Create Roadmap button * Roadmap type * chore: share progress modal * wip: share roadmap * wip: change visibility * chore: custom roadmap progress in activity * wip: custom roadmap share progress * chore: friend's roadmap * wip: custom roadmap skeleton * chore: roadmap title * Restricted Page * fix: skeleton loading width * Fix create roadmap button * chore: remove user id * chore: pick roadmap and share * chore: open new tab on create roadmap * chore: change share title * chore: use team id from params * chore: team roadmap create modal * chore: create team roadmap * chore: custom roadmap modal * chore: placeholde roadmaps * chore: roadmap hint * chore: visibility label * chore: public roadmap * chore: empty screen * chore: team progress * chore: create roadmap responsive * chore: form error * chore: multi user history * wip: manage custom roadmap * chore: empty roadmap list * chore: custom roadmap visit * chore: shared roadmaps * chore: shared roadmaps * chore: empty screen and topic title * chore: show progress bar * Implement Error in topic details * Add Modal close button * fix: link groups * Refactor roadmap creation * Refactor roadmap creation * Refactor team creation * Refactor team roadmaps * Refactor team creation roadmap selection * Refactor * Refactor team roadmap loading * Refactor team roadmaps * Refactor team roadmaps listing * Refactor Account dropdown * Updates * Refactor Account dropdown * Fix Team name overflow * Change Icon color * Update team dropdown * Minor UI fixes * Fix minor UI * Flicker fix in team dropdown * Roadmap action dropdown with responsiveness * Team roadmaps listing * Update team settings * Team roadmaps listing * fix: remove visibility change * Update roadmap options modal * Add dummy renderer * Add renderer script * Add generate renderer script * Add generate renderer * wip: add share settings * Update * Update UI * Update Minor UI * Fix team issue * Update Personal roadmaps UI * Add Roadmap Secret * Update teams type * Rearrange sections * Change Secret name * Add action button on roadmap detail page --------- Co-authored-by: Kamran Ahmed --- .env.example | 1 + .github/workflows/deploy.yml | 1 + .gitignore | 5 + package.json | 5 + pnpm-lock.yaml | 431 +++++++++++ renderer/index.tsx | 14 + renderer/renderer.ts | 5 + scripts/generate-renderer.sh | 31 + src/components/AccountSidebar.astro | 41 +- src/components/Activity/ActivityPage.tsx | 37 +- src/components/Activity/ResourceProgress.tsx | 17 +- src/components/AddTeamRoadmap.tsx | 2 +- src/components/Authenticator/authenticator.ts | 1 + src/components/CreateTeam/RoadmapSelector.tsx | 259 +++++-- .../CreateTeam/SelectRoadmapModal.tsx | 4 +- src/components/CreateTeam/Step0.tsx | 18 +- src/components/CreateTeam/Step1.tsx | 4 +- src/components/CreateTeam/Step2.tsx | 13 +- src/components/CreateTeam/Step3.tsx | 3 +- .../CreateTeam/UpdateTeamResourceModal.tsx | 16 +- .../CreateRoadmap/CreateRoadmapButton.tsx | 53 ++ .../CreateRoadmap/CreateRoadmapModal.tsx | 275 +++++++ .../CustomRoadmap/CustomRoadmap.tsx | 121 +++ src/components/CustomRoadmap/EmptyRoadmap.tsx | 12 + .../PersonalRoadmapActionDropdown.tsx | 94 +++ .../CustomRoadmap/PersonalRoadmapList.tsx | 238 ++++++ .../CustomRoadmap/ResourceProgressStats.tsx | 111 +++ .../CustomRoadmap/RestrictedPage.tsx | 52 ++ .../CustomRoadmap/RoadmapActionButton.tsx | 85 +++ .../CustomRoadmap/RoadmapHeader.tsx | 142 ++++ src/components/CustomRoadmap/RoadmapHint.tsx | 48 ++ .../CustomRoadmap/RoadmapListPage.tsx | 134 ++++ .../CustomRoadmap/RoadmapRenderer.css | 53 ++ .../CustomRoadmap/RoadmapRenderer.tsx | 177 +++++ .../CustomRoadmap/ShareRoadmapModal.tsx | 162 ++++ .../CustomRoadmap/SharedRoadmapList.tsx | 118 +++ .../CustomRoadmap/SkeletonRoadmapHeader.tsx | 28 + .../FeaturedItems/FeaturedItems.astro | 24 +- src/components/Friends/FriendProgressItem.tsx | 14 +- src/components/Friends/FriendsPage.tsx | 6 +- .../Friends/SidebarFriendsCounter.tsx | 7 +- .../HeroSection/FavoriteRoadmaps.tsx | 29 +- src/components/HeroSection/HeroRoadmaps.tsx | 86 ++- src/components/Modal.tsx | 46 ++ .../Navigation/AccountDropdown.astro | 4 +- src/components/Navigation/AccountDropdown.tsx | 52 ++ .../Navigation/AccountDropdownList.tsx | 49 ++ .../Navigation/DropdownTeamList.tsx | 110 +++ src/components/Navigation/Navigation.astro | 12 +- .../ShareOptions/CopyRoadmapLink.tsx | 64 ++ .../ShareOptions/ShareFriendList.tsx | 160 ++++ .../ShareOptions/ShareOptionsModal.tsx | 311 ++++++++ .../ShareOptions/ShareOptionsTab.tsx | 129 ++++ .../ShareOptions/ShareTeamMemberList.tsx | 164 +++++ .../ShareOptions/TransferToTeamList.tsx | 114 +++ src/components/ShareOptions/UserItem.tsx | 46 ++ src/components/Stepper.tsx | 9 +- src/components/TeamDropdown/TeamDropdown.tsx | 2 +- .../TeamMembers/MemberActionDropdown.tsx | 16 +- src/components/TeamMembers/TeamMemberItem.tsx | 20 +- .../TeamProgress/GroupRoadmapItem.tsx | 8 +- .../TeamProgress/MemberProgressItem.tsx | 14 +- .../TeamProgress/MemberProgressModal.tsx | 107 ++- .../TeamProgress/TeamProgressPage.tsx | 15 +- .../TeamRoadmap/CustomTeamRoadmap.tsx | 3 + .../TeamRoadmap/DefaultTeamRoadmap.tsx | 3 + src/components/TeamRoadmaps.tsx | 346 --------- .../TeamRoadmaps/PickRoadmapOptionModal.tsx | 39 + .../RoadmapActionDropdown.tsx | 93 +++ .../TeamRoadmapsList/TeamRoadmaps.tsx | 636 ++++++++++++++++ src/components/TeamsList.tsx | 70 +- src/components/Toast.tsx | 2 +- src/components/Tooltip.tsx | 61 ++ src/components/TopicDetail/TopicDetail.tsx | 191 ++++- .../UserProgress/ProgressShareButton.tsx | 19 +- .../UserProgress/UserProgressModal.tsx | 23 + .../ai-data-scientist/ai-data-scientist.json | 690 +++++++++--------- src/env.d.ts | 1 + src/hooks/use-load-topic.ts | 11 +- src/layouts/AccountLayout.astro | 2 +- src/layouts/BaseLayout.astro | 10 +- src/lib/classname.ts | 6 + src/lib/http.ts | 2 +- src/lib/jwt.ts | 10 + src/lib/resource-progress.ts | 83 ++- src/pages/[roadmapId]/[...topicId].astro | 26 +- src/pages/[roadmapId]/index.astro | 6 +- src/pages/account/roadmaps.astro | 15 + .../[bestPracticeId]/index.astro | 5 +- src/pages/index.astro | 2 + src/pages/r/index.astro | 22 + src/pages/team/roadmaps.astro | 2 +- src/stores/roadmap.ts | 12 + tailwind.config.cjs | 12 +- 94 files changed, 5760 insertions(+), 1072 deletions(-) create mode 100644 renderer/index.tsx create mode 100644 renderer/renderer.ts create mode 100644 scripts/generate-renderer.sh create mode 100644 src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx create mode 100644 src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx create mode 100644 src/components/CustomRoadmap/CustomRoadmap.tsx create mode 100644 src/components/CustomRoadmap/EmptyRoadmap.tsx create mode 100644 src/components/CustomRoadmap/PersonalRoadmapActionDropdown.tsx create mode 100644 src/components/CustomRoadmap/PersonalRoadmapList.tsx create mode 100644 src/components/CustomRoadmap/ResourceProgressStats.tsx create mode 100644 src/components/CustomRoadmap/RestrictedPage.tsx create mode 100644 src/components/CustomRoadmap/RoadmapActionButton.tsx create mode 100644 src/components/CustomRoadmap/RoadmapHeader.tsx create mode 100644 src/components/CustomRoadmap/RoadmapHint.tsx create mode 100644 src/components/CustomRoadmap/RoadmapListPage.tsx create mode 100644 src/components/CustomRoadmap/RoadmapRenderer.css create mode 100644 src/components/CustomRoadmap/RoadmapRenderer.tsx create mode 100644 src/components/CustomRoadmap/ShareRoadmapModal.tsx create mode 100644 src/components/CustomRoadmap/SharedRoadmapList.tsx create mode 100644 src/components/CustomRoadmap/SkeletonRoadmapHeader.tsx create mode 100644 src/components/Modal.tsx create mode 100644 src/components/Navigation/AccountDropdown.tsx create mode 100644 src/components/Navigation/AccountDropdownList.tsx create mode 100644 src/components/Navigation/DropdownTeamList.tsx create mode 100644 src/components/ShareOptions/CopyRoadmapLink.tsx create mode 100644 src/components/ShareOptions/ShareFriendList.tsx create mode 100644 src/components/ShareOptions/ShareOptionsModal.tsx create mode 100644 src/components/ShareOptions/ShareOptionsTab.tsx create mode 100644 src/components/ShareOptions/ShareTeamMemberList.tsx create mode 100644 src/components/ShareOptions/TransferToTeamList.tsx create mode 100644 src/components/ShareOptions/UserItem.tsx create mode 100644 src/components/TeamRoadmap/CustomTeamRoadmap.tsx create mode 100644 src/components/TeamRoadmap/DefaultTeamRoadmap.tsx delete mode 100644 src/components/TeamRoadmaps.tsx create mode 100644 src/components/TeamRoadmaps/PickRoadmapOptionModal.tsx create mode 100644 src/components/TeamRoadmapsList/RoadmapActionDropdown.tsx create mode 100644 src/components/TeamRoadmapsList/TeamRoadmaps.tsx create mode 100644 src/components/Tooltip.tsx create mode 100644 src/lib/classname.ts create mode 100644 src/pages/account/roadmaps.astro create mode 100644 src/pages/r/index.astro create mode 100644 src/stores/roadmap.ts diff --git a/.env.example b/.env.example index cb12ca496..2f9faa2d4 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ PUBLIC_API_URL=http://api.roadmap.sh PUBLIC_AVATAR_BASE_URL=https://dodrc8eu8m09s.cloudfront.net/avatars +PUBLIC_EDITOR_APP_URL= \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e4f5ed0ff..01236034c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,6 +27,7 @@ jobs: pnpm install - name: Generate meta and build run: | + npm run generate-renderer npm run build touch ./dist/.nojekyll echo 'roadmap.sh' > ./dist/CNAME diff --git a/.gitignore b/.gitignore index c3fd092b0..2bb5e6479 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.temp # build output dist/ @@ -27,3 +28,7 @@ pnpm-debug.log* /playwright/.cache/ tests-examples *.csv + +/renderer/* +!/renderer/index.tsx +!/renderer/renderer.ts \ No newline at end of file diff --git a/package.json b/package.json index c465404f0..9ebd63899 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "roadmap-content": "node scripts/roadmap-content.cjs", "best-practice-dirs": "node scripts/best-practice-dirs.cjs", "best-practice-content": "node scripts/best-practice-content.cjs", + "generate-renderer": "sh scripts/generate-renderer.sh", "test:e2e": "playwright test" }, "dependencies": { @@ -30,10 +31,12 @@ "@types/react-dom": "^18.0.6", "astro": "^3.0.5", "astro-compress": "^2.0.8", + "clsx": "^2.0.0", "dracula-prism": "^2.1.13", "jose": "^4.14.4", "js-cookie": "^3.0.5", "lucide-react": "^0.274.0", + "nanoid": "^4.0.2", "nanostores": "^0.9.2", "node-html-parser": "^6.1.5", "npm-check-updates": "^16.10.12", @@ -41,9 +44,11 @@ "react": "^18.0.0", "react-confetti": "^6.1.0", "react-dom": "^18.0.0", + "reactflow": "^11.8.3", "rehype-external-links": "^2.1.0", "roadmap-renderer": "^1.0.6", "slugify": "^1.6.6", + "tailwind-merge": "^1.14.0", "tailwindcss": "^3.3.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebcfd86a7..8f7b707f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: astro-compress: specifier: ^2.0.8 version: 2.0.8 + clsx: + specifier: ^2.0.0 + version: 2.0.0 dracula-prism: specifier: ^2.1.13 version: 2.1.13 @@ -44,6 +47,9 @@ dependencies: lucide-react: specifier: ^0.274.0 version: 0.274.0(react@18.0.0) + nanoid: + specifier: ^4.0.2 + version: 4.0.2 nanostores: specifier: ^0.9.2 version: 0.9.2 @@ -65,6 +71,9 @@ dependencies: react-dom: specifier: ^18.0.0 version: 18.0.0(react@18.0.0) + reactflow: + specifier: ^11.8.3 + version: 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) rehype-external-links: specifier: ^2.1.0 version: 2.1.0 @@ -74,6 +83,9 @@ dependencies: slugify: specifier: ^1.6.6 version: 1.6.6 + tailwind-merge: + specifier: ^1.14.0 + version: 1.14.0 tailwindcss: specifier: ^3.3.3 version: 3.3.3 @@ -1056,6 +1068,114 @@ packages: config-chain: 1.1.13 dev: false + /@reactflow/background@11.2.8(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-5o41N2LygiNC2/Pk8Ak2rIJjXbKHfQ23/Y9LFsnAlufqwdzFqKA8txExpsMoPVHHlbAdA/xpQaMuoChGPqmyDw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + classcat: 5.0.4 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/controls@11.1.19(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-Vo0LFfAYjiSRMLEII/aeBo+1MT2a0Yc7iLVnkuRTLzChC0EX+A2Fa+JlzeOEYKxXlN4qcDxckRNGR7092v1HOQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + classcat: 5.0.4 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/core@11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-y6DN8Wy4V4KQBGHFqlj9zWRjLJU6CgdnVwWaEA/PdDg/YUkFBMpZnXqTs60czinoA2rAcvsz50syLTPsj5e+Wg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@types/d3': 7.4.0 + '@types/d3-drag': 3.0.3 + '@types/d3-selection': 3.0.6 + '@types/d3-zoom': 3.0.4 + classcat: 5.0.4 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/minimap@11.6.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-PSA28dk09RnBHOA1zb45fjQXz3UozSJZmsIpgq49O3trfVFlSgRapxNdGsughWLs7/emg2M5jmi6Vc+ejcfjvQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@types/d3-selection': 3.0.6 + '@types/d3-zoom': 3.0.4 + classcat: 5.0.4 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/node-resizer@2.1.5(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-z/hJlsptd2vTx13wKouqvN/Kln08qbkA+YTJLohc2aJ6rx3oGn9yX4E4IqNxhA7zNqYEdrnc1JTEA//ifh9z3w==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + classcat: 5.0.4 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/node-toolbar@1.2.7(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-vs+Wg1tjy3SuD7eoeTqEtscBfE9RY+APqC28urVvftkrtsN7KlnoQjqDG6aE45jWP4z+8bvFizRWjAhxysNLkg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + classcat: 5.0.4 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + zustand: 4.4.1(@types/react@18.0.21)(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /@sigstore/bundle@1.1.0: resolution: {integrity: sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1175,6 +1295,185 @@ packages: '@types/css-tree': 2.3.1 dev: false + /@types/d3-array@3.0.7: + resolution: {integrity: sha512-4/Q0FckQ8TBjsB0VdGFemJOG8BLXUB2KKlL0VmZ+eOYeOnTb/wDRQqYWpBmQ6IlvWkXwkYiot+n9Px2aTJ7zGQ==} + dev: false + + /@types/d3-axis@3.0.3: + resolution: {integrity: sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-brush@3.0.3: + resolution: {integrity: sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-chord@3.0.3: + resolution: {integrity: sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw==} + dev: false + + /@types/d3-color@3.1.0: + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + dev: false + + /@types/d3-contour@3.0.3: + resolution: {integrity: sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw==} + dependencies: + '@types/d3-array': 3.0.7 + '@types/geojson': 7946.0.10 + dev: false + + /@types/d3-delaunay@6.0.1: + resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==} + dev: false + + /@types/d3-dispatch@3.0.3: + resolution: {integrity: sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug==} + dev: false + + /@types/d3-drag@3.0.3: + resolution: {integrity: sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-dsv@3.0.2: + resolution: {integrity: sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g==} + dev: false + + /@types/d3-ease@3.0.0: + resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==} + dev: false + + /@types/d3-fetch@3.0.3: + resolution: {integrity: sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA==} + dependencies: + '@types/d3-dsv': 3.0.2 + dev: false + + /@types/d3-force@3.0.5: + resolution: {integrity: sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug==} + dev: false + + /@types/d3-format@3.0.1: + resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==} + dev: false + + /@types/d3-geo@3.0.4: + resolution: {integrity: sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ==} + dependencies: + '@types/geojson': 7946.0.10 + dev: false + + /@types/d3-hierarchy@3.1.3: + resolution: {integrity: sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw==} + dev: false + + /@types/d3-interpolate@3.0.1: + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + dependencies: + '@types/d3-color': 3.1.0 + dev: false + + /@types/d3-path@3.0.0: + resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==} + dev: false + + /@types/d3-polygon@3.0.0: + resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==} + dev: false + + /@types/d3-quadtree@3.0.2: + resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==} + dev: false + + /@types/d3-random@3.0.1: + resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==} + dev: false + + /@types/d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==} + dev: false + + /@types/d3-scale@4.0.4: + resolution: {integrity: sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==} + dependencies: + '@types/d3-time': 3.0.0 + dev: false + + /@types/d3-selection@3.0.6: + resolution: {integrity: sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w==} + dev: false + + /@types/d3-shape@3.1.2: + resolution: {integrity: sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w==} + dependencies: + '@types/d3-path': 3.0.0 + dev: false + + /@types/d3-time-format@4.0.0: + resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==} + dev: false + + /@types/d3-time@3.0.0: + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + dev: false + + /@types/d3-timer@3.0.0: + resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==} + dev: false + + /@types/d3-transition@3.0.4: + resolution: {integrity: sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-zoom@3.0.4: + resolution: {integrity: sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA==} + dependencies: + '@types/d3-interpolate': 3.0.1 + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3@7.4.0: + resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==} + dependencies: + '@types/d3-array': 3.0.7 + '@types/d3-axis': 3.0.3 + '@types/d3-brush': 3.0.3 + '@types/d3-chord': 3.0.3 + '@types/d3-color': 3.1.0 + '@types/d3-contour': 3.0.3 + '@types/d3-delaunay': 6.0.1 + '@types/d3-dispatch': 3.0.3 + '@types/d3-drag': 3.0.3 + '@types/d3-dsv': 3.0.2 + '@types/d3-ease': 3.0.0 + '@types/d3-fetch': 3.0.3 + '@types/d3-force': 3.0.5 + '@types/d3-format': 3.0.1 + '@types/d3-geo': 3.0.4 + '@types/d3-hierarchy': 3.1.3 + '@types/d3-interpolate': 3.0.1 + '@types/d3-path': 3.0.0 + '@types/d3-polygon': 3.0.0 + '@types/d3-quadtree': 3.0.2 + '@types/d3-random': 3.0.1 + '@types/d3-scale': 4.0.4 + '@types/d3-scale-chromatic': 3.0.0 + '@types/d3-selection': 3.0.6 + '@types/d3-shape': 3.1.2 + '@types/d3-time': 3.0.0 + '@types/d3-time-format': 4.0.0 + '@types/d3-timer': 3.0.0 + '@types/d3-transition': 3.0.4 + '@types/d3-zoom': 3.0.4 + dev: false + /@types/debug@4.1.8: resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} dependencies: @@ -1185,6 +1484,10 @@ packages: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: false + /@types/geojson@7946.0.10: + resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==} + dev: false + /@types/hast@2.3.5: resolution: {integrity: sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==} dependencies: @@ -1766,6 +2069,10 @@ packages: engines: {node: '>=8'} dev: false + /classcat@5.0.4: + resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==} + dev: false + /clean-css@5.3.2: resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==} engines: {node: '>= 10.0'} @@ -1991,6 +2298,71 @@ packages: minimist: 1.2.8 dev: true + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /d3-transition@3.0.1(d3-selection@3.0.0): + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -2839,6 +3211,7 @@ packages: /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + requiresBuild: true dependencies: safer-buffer: 2.1.2 dev: false @@ -3887,6 +4260,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@4.0.2: + resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: false + /nanostores@0.9.2: resolution: {integrity: sha512-wfKlqLGtOYV9+qzGveqDOSWZUBgTeMr/g+JzfV/GofXQ//0wp0cgHF+QBVlmNH/JW9YA9QN+vR6N0vpniPpARA==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} @@ -4670,6 +5049,25 @@ packages: loose-envify: 1.4.0 dev: false + /reactflow@11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-wuVxJOFqi1vhA4WAEJLK0JWx2TsTiWpxTXTRp/wvpqKInQgQcB49I2QNyNYsKJCQ6jjXektS7H+LXoaVK/pG4A==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/background': 11.2.8(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@reactflow/controls': 11.1.19(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@reactflow/core': 11.8.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@reactflow/minimap': 11.6.3(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@reactflow/node-resizer': 2.1.5(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + '@reactflow/node-toolbar': 1.2.7(@types/react@18.0.21)(react-dom@18.0.0)(react@18.0.0) + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -4942,6 +5340,7 @@ packages: /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + requiresBuild: true dev: false optional: true @@ -5354,6 +5753,10 @@ packages: picocolors: 1.0.0 dev: false + /tailwind-merge@1.14.0: + resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} + dev: false + /tailwindcss@3.3.3: resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==} engines: {node: '>=14.0.0'} @@ -5683,6 +6086,14 @@ packages: xdg-basedir: 5.1.0 dev: false + /use-sync-external-store@1.2.0(react@18.0.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.0.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5912,6 +6323,26 @@ packages: resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} dev: false + /zustand@4.4.1(@types/react@18.0.21)(react@18.0.0): + resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + dependencies: + '@types/react': 18.0.21 + react: 18.0.0 + use-sync-external-store: 1.2.0(react@18.0.0) + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false diff --git a/renderer/index.tsx b/renderer/index.tsx new file mode 100644 index 000000000..aa8d1947a --- /dev/null +++ b/renderer/index.tsx @@ -0,0 +1,14 @@ +export function Renderer(props: any) { + return ( +
+

Private Component

+

+ Renderer is a private component. If you are a collaborator and have + access to it. Run the following command: +

+ + npm run generate-renderer + +
+ ); +} diff --git a/renderer/renderer.ts b/renderer/renderer.ts new file mode 100644 index 000000000..b83d71068 --- /dev/null +++ b/renderer/renderer.ts @@ -0,0 +1,5 @@ +export function renderFlowJSON(data: any, options?: any) { + console.warn("renderFlowJSON is not implemented"); + console.warn("run the following command to generate the renderer:"); + console.warn("> npm run generate-renderer"); +} diff --git a/scripts/generate-renderer.sh b/scripts/generate-renderer.sh new file mode 100644 index 000000000..80f41c92a --- /dev/null +++ b/scripts/generate-renderer.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -e + +rm -rf .temp +git clone git@github.com:roadmapsh/web-draw.git .temp/web-draw + +rm -rf renderer +mkdir renderer + +# copy the files at /src/editor/renderer/* to /renderer +# while replacing any existing files +cp -rf .temp/web-draw/src/editor/renderer/* renderer + +# Add @ts-nocheck to the top of each ts and tsx file +# so that the typescript compiler doesn't complain +# about the missing types +find renderer -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 | while IFS= read -r -d '' file; do + if [ -f "$file" ]; then + echo "// @ts-nocheck" > temp + cat "$file" >> temp + mv temp "$file" + echo "Added @ts-nocheck to $file" + fi +done + +# remove the temporary directory +rm -rf .temp + +# ignore the worktree changes for the renderer directory +git update-index --skip-worktree renderer/* diff --git a/src/components/AccountSidebar.astro b/src/components/AccountSidebar.astro index 3ac54317b..5f5d24e0b 100644 --- a/src/components/AccountSidebar.astro +++ b/src/components/AccountSidebar.astro @@ -2,6 +2,7 @@ import AstroIcon from './AstroIcon.astro'; import { TeamDropdown } from './TeamDropdown/TeamDropdown'; import { SidebarFriendsCounter } from './Friends/SidebarFriendsCounter'; +import { Map } from 'lucide-react'; export interface Props { activePageId: string; @@ -26,10 +27,21 @@ const sidebarLinks = [ href: '/account/friends', title: 'Friends', id: 'friends', + isNew: false, + icon: { + glyph: 'users', + classes: 'h-4 w-4', + }, + }, + { + href: '/account/roadmaps', + title: 'Roadmaps', + id: 'roadmaps', isNew: true, icon: { glyph: 'users', classes: 'h-4 w-4', + component: Map, }, }, { @@ -100,10 +112,16 @@ const sidebarLinks = [ isActive ? 'bg-slate-100' : '' }`} > - + {sidebarLink.icon.component ? ( + + ) : ( + + )} {sidebarLink.title} @@ -136,15 +154,20 @@ const sidebarLinks = [ }`} > - + {sidebarLink.icon.component ? ( + + ) : ( + + )} {sidebarLink.title} {sidebarLink.isNew && - sidebarLink.id !== 'friends' && !isActive && ( diff --git a/src/components/Activity/ActivityPage.tsx b/src/components/Activity/ActivityPage.tsx index be2a9914c..da8da197d 100644 --- a/src/components/Activity/ActivityPage.tsx +++ b/src/components/Activity/ActivityPage.tsx @@ -5,6 +5,17 @@ import { ResourceProgress } from './ResourceProgress'; import { pageProgressMessage } from '../../stores/page'; import { EmptyActivity } from './EmptyActivity'; +type ProgressResponse = { + updatedAt: string; + title: string; + id: string; + learning: number; + skipped: number; + done: number; + total: number; + isCustomResource: boolean; +}; + export type ActivityResponse = { done: { today: number; @@ -13,24 +24,9 @@ export type ActivityResponse = { learning: { today: number; total: number; - roadmaps: { - title: string; - id: string; - learning: number; - done: number; - total: number; - skipped: number; - updatedAt: string; - }[]; - bestPractices: { - title: string; - id: string; - learning: number; - done: number; - skipped: number; - total: number; - updatedAt: string; - }[]; + roadmaps: ProgressResponse[]; + bestPractices: ProgressResponse[]; + customs: ProgressResponse[]; }; streak: { count: number; @@ -110,7 +106,8 @@ export function ActivityPage() { }) .map((roadmap) => ( ( void; showClearButton?: boolean; + isCustomResource: boolean; }; export function ResourceProgress(props: ResourceProgressType) { - const { showClearButton = true } = props; + const { showClearButton = true, isCustomResource } = props; const toast = useToast(); const [isClearing, setIsClearing] = useState(false); const [isConfirming, setIsConfirming] = useState(false); + const userId = getUser()?.id; + const { updatedAt, resourceType, @@ -52,8 +56,8 @@ export function ResourceProgress(props: ResourceProgressType) { return; } - localStorage.removeItem(`${resourceType}-${resourceId}-favorite`); - localStorage.removeItem(`${resourceType}-${resourceId}-progress`); + localStorage.removeItem(`${resourceType}-${resourceId}-${userId}-favorite`); + localStorage.removeItem(`${resourceType}-${resourceId}-${userId}-progress`); setIsClearing(false); setIsConfirming(false); @@ -62,11 +66,15 @@ export function ResourceProgress(props: ResourceProgressType) { } } - const url = + let url = resourceType === 'roadmap' ? `/${resourceId}` : `/best-practices/${resourceId}`; + if (isCustomResource) { + url = `/r?id=${resourceId}`; + } + const totalMarked = doneCount + skippedCount; const progressPercentage = Math.round((totalMarked / totalCount) * 100); @@ -112,6 +120,7 @@ export function ResourceProgress(props: ResourceProgressType) { void; + teamResources: TeamResourceConfig; + setTeamResources: (config: TeamResourceConfig) => void; }; export function RoadmapSelector(props: RoadmapSelectorProps) { - const { teamId, teamResourceConfig = [], setTeamResourceConfig } = props; + const { teamId, teamResources = [], setTeamResources } = props; + const toast = useToast(); + const [removingRoadmapId, setRemovingRoadmapId] = useState(''); const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false); const [allRoadmaps, setAllRoadmaps] = useState([]); const [changingRoadmapId, setChangingRoadmapId] = useState(''); + const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false); + const [error, setError] = useState(''); async function loadAllRoadmaps() { const { error, response } = await httpGet(`/pages.json`); if (error) { + toast.error(error.message || 'Something went wrong. Please try again!'); setError(error.message || 'Something went wrong. Please try again!'); return; } @@ -72,7 +87,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { return; } - setTeamResourceConfig(response); + setTeamResources(response); } async function onRemove(resourceId: string) { @@ -106,13 +121,25 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { return; } - setTeamResourceConfig(response); + setTeamResources(response); } useEffect(() => { - loadAllRoadmaps().finally(); + loadAllRoadmaps().finally(() => {}); }, []); + function handleCustomRoadmapCreated(roadmap: RoadmapDocument) { + const { _id: roadmapId } = roadmap; + if (!roadmapId) { + return; + } + + loadAllRoadmaps().finally(() => {}); + addTeamResource(roadmapId).finally(() => { + pageProgressMessage.set(''); + }); + } + return (
{changingRoadmapId && ( @@ -121,9 +148,9 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { resourceId={changingRoadmapId} resourceType={'roadmap'} teamId={teamId} - setTeamResourceConfig={setTeamResourceConfig} + setTeamResourceConfig={setTeamResources} defaultRemovedItems={ - teamResourceConfig.find((c) => c.resourceId === changingRoadmapId) + teamResources.find((c) => c.resourceId === changingRoadmapId) ?.removed || [] } /> @@ -131,7 +158,7 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { {showSelectRoadmapModal && ( setShowSelectRoadmapModal(false)} - teamResourceConfig={teamResourceConfig} + teamResourceConfig={teamResources} allRoadmaps={allRoadmaps} teamId={teamId} onRoadmapAdd={(roadmapId) => { @@ -145,72 +172,170 @@ export function RoadmapSelector(props: RoadmapSelectorProps) { /> )} -
- + {isCreatingRoadmap && ( + setIsCreatingRoadmap(false)} + onCreated={(roadmap: RoadmapDocument) => { + handleCustomRoadmapCreated(roadmap); + setIsCreatingRoadmap(false); + }} + /> + )} + + + + or + +
- {!teamResourceConfig.length && ( -

- No roadmaps selected. -

+ {!teamResources.length && ( +
+ +

No roadmaps selected.

+

+ Pick from{' '} + setShowSelectRoadmapModal(true)} + className="cursor-pointer underline" + > + our roadmaps + {' '} + or{' '} + { + setIsCreatingRoadmap(true); + }} + className="cursor-pointer underline" + > + create a new one + + . +

+
)} - {teamResourceConfig.length > 0 && ( -
- {teamResourceConfig.map(({ resourceId, removed: removedTopics }) => { - const roadmapTitle = - allRoadmaps.find((roadmap) => roadmap.id === resourceId)?.title || - '...'; - - return ( -
-
- - {roadmapTitle} - - {removedTopics.length > 0 ? ( - - {removedTopics.length} topic - {removedTopics.length > 1 ? 's' : ''} removed - - ) : ( - - No changes made .. + {teamResources.length > 0 && ( +
+ {teamResources.map( + ({ + isCustomResource, + title: roadmapTitle, + resourceId, + removed: removedTopics, + topics, + }) => { + return ( +
+
+ + {roadmapTitle} + {removedTopics.length > 0 || (topics && topics > 0) ? ( + + {isCustomResource ? ( + <> + Custom · {topics} topic + {topics && topics > 1 ? 's' : ''} + + ) : ( + <> + {removedTopics.length} topic + {removedTopics.length > 1 ? 's' : ''} removed + + )} + + ) : ( + + {isCustomResource + ? 'Placeholder roadmap.' + : 'No changes made ..'} + + )} +
+ + {removingRoadmapId === resourceId && ( +
+ + Are you sure?{' '} + {' '} + + +
)} -
+ {(!removingRoadmapId || removingRoadmapId !== resourceId) && ( +
+ -
- - - + +
+ )}
-
- ); - })} + ); + } + )}
)}
diff --git a/src/components/CreateTeam/SelectRoadmapModal.tsx b/src/components/CreateTeam/SelectRoadmapModal.tsx index f74c8d743..a96efc606 100644 --- a/src/components/CreateTeam/SelectRoadmapModal.tsx +++ b/src/components/CreateTeam/SelectRoadmapModal.tsx @@ -100,12 +100,13 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) { {roleBasedRoadmaps.length > 0 && (
{roleBasedRoadmaps.map((roadmap) => { - const isSelected = !!teamResourceConfig.find( + const isSelected = !!teamResourceConfig?.find( (r) => r.resourceId === roadmap.id ); return ( { @@ -131,6 +132,7 @@ export function SelectRoadmapModal(props: SelectRoadmapModalProps) { return ( { diff --git a/src/components/CreateTeam/Step0.tsx b/src/components/CreateTeam/Step0.tsx index e9ebb4193..3a72fd564 100644 --- a/src/components/CreateTeam/Step0.tsx +++ b/src/components/CreateTeam/Step0.tsx @@ -10,13 +10,15 @@ export const validTeamTypes = [ value: 'company', label: 'Company', icon: BuildingIcon.src, - description: 'Track the skills and learning progress of the tech team at your company', + description: + 'Track the skills and learning progress of the tech team at your company', }, { value: 'study_group', label: 'Study Group', icon: UsersIcon.src, - description: 'Invite your friends or course-mates and track your learning progress together', + description: + 'Invite your friends or course-mates and track your learning progress together', }, ] as const; @@ -70,10 +72,11 @@ export function Step0(props: Step0Props) { return ( <> -
+
{validTeamTypes.map((validTeamType) => ( @@ -100,11 +104,11 @@ export function Step0(props: Step0Props) { {/*Error message*/} {error &&
{error}
} -
+ diff --git a/src/components/CreateTeam/Step2.tsx b/src/components/CreateTeam/Step2.tsx index 14b5d587c..9efc3174e 100644 --- a/src/components/CreateTeam/Step2.tsx +++ b/src/components/CreateTeam/Step2.tsx @@ -17,7 +17,9 @@ export function Step2(props: Step2Props) { <>
-

Select Roadmaps

+

+ Select Roadmaps +

You can always add and customize your roadmaps later.

@@ -25,12 +27,12 @@ export function Step2(props: Step2Props) {
-
+
+ + ); +} diff --git a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx new file mode 100644 index 000000000..98cfae263 --- /dev/null +++ b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx @@ -0,0 +1,275 @@ +import { + type FormEvent, + type MouseEvent, + useEffect, + useRef, + useState, +} from 'react'; +import { Loader2 } from 'lucide-react'; +import { Modal } from '../../Modal'; +import { useToast } from '../../../hooks/use-toast'; +import { httpPost } from '../../../lib/http'; +import { cn } from '../../../lib/classname'; +import { allowedVisibilityLabels } from '../ShareRoadmapModal'; + +export const allowedRoadmapVisibility = [ + 'me', + 'friends', + 'team', + 'public', +] as const; +export type AllowedRoadmapVisibility = + (typeof allowedRoadmapVisibility)[number]; +export const allowedCustomRoadmapType = ['role', 'skill'] as const; +export type AllowedCustomRoadmapType = + (typeof allowedCustomRoadmapType)[number]; + +export interface RoadmapDocument { + _id?: string; + title: string; + description?: string; + creatorId: string; + teamId?: string; + type: AllowedCustomRoadmapType; + visibility: AllowedRoadmapVisibility; + sharedFriendIds?: string[]; + sharedTeamMemberIds?: string[]; + nodes: any[]; + edges: any[]; + createdAt: Date; + updatedAt: Date; + canManage: boolean; + isCustomResource: boolean; +} + +interface CreateRoadmapModalProps { + onClose: () => void; + onCreated?: (roadmap: RoadmapDocument) => void; + teamId?: string; + type?: AllowedCustomRoadmapType; + visibility?: AllowedRoadmapVisibility; +} + +export function CreateRoadmapModal(props: CreateRoadmapModalProps) { + const { onClose, onCreated, teamId, type: defaultType = 'role' } = props; + + const titleRef = useRef(null); + const toast = useToast(); + + const [isLoading, setIsLoading] = useState(false); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [type, setType] = useState(defaultType); + const isInvalidDescription = description?.trim().length > 80; + + async function handleSubmit( + e: FormEvent | MouseEvent, + redirect: boolean = true + ) { + e.preventDefault(); + if (isLoading) { + return; + } + + if (title.trim() === '' || isInvalidDescription || !type) { + toast.error('Please fill all the fields'); + return; + } + + setIsLoading(true); + const { response, error } = await httpPost( + `${import.meta.env.PUBLIC_API_URL}/v1-create-roadmap`, + { + title, + description, + type, + ...(teamId && { + teamId, + }), + nodes: [], + edges: [], + } + ); + + if (error) { + setIsLoading(false); + toast.error(error?.message || 'Something went wrong, please try again'); + return; + } + + toast.success('Roadmap created successfully'); + if (redirect) { + window.location.href = `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${ + response?._id + }`; + return; + } + + if (onCreated) { + onCreated(response as RoadmapDocument); + return; + } + + onClose(); + + setTitle(''); + setDescription(''); + setType('role'); + setIsLoading(false); + } + + useEffect(() => { + titleRef.current?.focus(); + }, []); + + return ( + +
+

Create Roadmap

+

+ Add a title and description to your roadmap. +

+
+
+
+ +
+ setTitle(e.target.value)} + /> +
+
+
+ +
+