From 43849e758eea04858fadf052e56f09d3fdaf0f2d Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Wed, 27 Nov 2024 13:07:59 +0600 Subject: [PATCH] feat: showcase roadmap (#7791) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * fix: status issue * feat: update UI * wip * wip: showcase status * wip: showcase listing * feat: update showcase status * chore: update roadmap content json (#7738) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * Fix issue in sticky top ad * Add preloading of ad image * feat(backend): update unit testing node resources (#7743) * feat: container orchestration * Update container-orchestration@Yq8kVoRf20aL_o4VZU5--.md Simplified content and added working links to resources for better clarity and learning. * Update container-orchestration@Yq8kVoRf20aL_o4VZU5--.md Replace content and added working links to resources for better clarity and learning. * Update container-orchestration@Yq8kVoRf20aL_o4VZU5--.md * Update src/data/roadmaps/devops/content/container-orchestration@Yq8kVoRf20aL_o4VZU5--.md --------- Co-authored-by: Arik Chakma * docs: fix typos and improve grammar in documentation (#7747) Corrects typos and grammatical errors in various markdown files to enhance clarity and readability. * feat: add PearAI code editor Added PearAI to the list of AI Code Editors (An Open Source Option for developers!) * chore: update roadmap content json (#7751) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * feat: center of mass explain video (#7754) video addition explaining COM better * Ad new changelog entry * Update C# link to correct URL (#7757) * Add engineering manager roadmap * chore: update roadmap content json (#7758) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * Update frontend FAQs (#7764) Tweaked the first two Qs * Update DevOps skills (#7763) Added internal refs * Add ref to DevOps roadmap in guide (#7762) Added roadmap ref. * Add engineering manager roadmap * Update engineering manager roadmap content * Update engineering manager roadmap * Add content to engineering manager roadmap * chore: update roadmap content json (#7768) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * fix: postgresql link (#7766) * fix(typo): comma todo-list-api.md (#7772) * Add new link of Redis in FullStack (#7771) * chore: update roadmap content json (#7778) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * Add content to vue.js performance (#7777) * Update performance@f7N4pAp_jBlT8_8owAcbG.md * Update src/data/roadmaps/vue/content/performance@f7N4pAp_jBlT8_8owAcbG.md --------- Co-authored-by: Kamran Ahmed * Update resources for Authentication (#7745) * Update authentication-vs-authorization@WG7DdsxESm31VcLFfkVTz.md replaced a wrong article with one about biometrics * Update understand-common-exploit-frameworks@Lg7mz4zeCToEzZBFxYuaU.md link redirects to a Thai gambling game site * Add resource for rest-assured (#7737) ## Content I’ve added a beginner-friendly article, A Guide to REST-assured, from Baeldung to the REST Assured section. If there’s anything that doesn’t meet the format, please feel free to comment. Thanks😊. ## Issue Fixed #7736 * Add UX design resource (#7710) * Update conceptual-design@r6D07cN0Mg4YXsiRSrl1_.md I have added an article by Dan Nessler on How to apply a design thinking, HCD, UX or any creative process from scratch which is a how-to article aims at providing designers, creative thinkers or even project managers with a tool to set up, frame, organise, structure, run or manage design challenges, and projects: The Double Diamond revamped. * Update conceptual-design@r6D07cN0Mg4YXsiRSrl1_.md I have added an article by Dan Nessler on How to apply a design thinking, HCD, UX or any creative process from scratch which is a how-to article aims at providing designers, creative thinkers or even project managers with a tool to set up, frame, organise, structure, run or manage design challenges, and projects: The Double Diamond revamped. * Add user personas resource to UX design (#7709) Added more resources from IxD Foundation and NN group. Co-authored-by: Shivam Kumar <85393390+TinyTijil@users.noreply.github.com> * Add linked in content (#7695) * Update linkedin@6UR59TigEZ0NaixbaUIqn.md * Update src/data/roadmaps/devrel/content/linkedin@6UR59TigEZ0NaixbaUIqn.md --------- Co-authored-by: Kamran Ahmed * Added article on AuthN vs AuthZ (#7694) Added a guide on the difference between authentication and authorization, since these terms are often confused. * Add a video to the Decentralization section (#7692) * adding a video to the Decentralization section * adding a video to the Decentralization section * Fixes typo in 104-proc-priorities.md (#7684) Old: renice +5 New: renice -5 From my research, after reading the topic in the Linux roadmap, it didnt make sense that increasing the priority of a process was made by +5, the topic said that a negative number makes the priority higher, so do many articles on the internet. * Add bastion host and file integrity checker idea * Add pomodoro timer project idea * Add project idea for quiz app * chore: update roadmap content json (#7785) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * fix typo in dockerhub alternatives (#7780) Co-authored-by: Fabio Stabile * Add content to engineering manager roadmap (#7779) * Update system-design-and-architecture@iX4HPgoiEbc_gze1A01n4.md * Update src/data/roadmaps/engineering-manager/content/system-design-and-architecture@iX4HPgoiEbc_gze1A01n4.md * Update src/data/roadmaps/engineering-manager/content/system-design-and-architecture@iX4HPgoiEbc_gze1A01n4.md --------- Co-authored-by: Kamran Ahmed * Add project idea for stories feature * Add weather app project idea * Update project ideas * Add engineering manager roadmap content * Update engineering manager roadmap content * Add DevOps best practices guide * Add AI Engineer introduction video (#7788) * Added Introduction Video * Changed formatting * Update src/data/roadmaps/ai-engineer/content/introduction@_hYN0gEi9BL24nptEtXWU.md --------- Co-authored-by: Kamran Ahmed * chore: update roadmap content json (#7789) Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> * Add devops automation tools * Add featuring functionality --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: kamranahmedse <4921183+kamranahmedse@users.noreply.github.com> Co-authored-by: Kamran Ahmed Co-authored-by: Rogério Ferreira de Souza Co-authored-by: Jawher Kl Co-authored-by: garyellow Co-authored-by: Nang Co-authored-by: FormerlyWD <156501761+FormerlyWD@users.noreply.github.com> Co-authored-by: dudi vaichledere <117650526+dudi-w@users.noreply.github.com> Co-authored-by: Ed Lan <165309301+Edlan01@users.noreply.github.com> Co-authored-by: elias_sisay <87943132+eliassisay@users.noreply.github.com> Co-authored-by: feelsgoodfrog Co-authored-by: Gustavo Martins Pereira Co-authored-by: Maksymilian Co-authored-by: b4haa7 <69992780+88BahaaAdel88@users.noreply.github.com> Co-authored-by: Wick Dynex <1328032567@qq.com> Co-authored-by: Shivam Kumar <85393390+kshivam14@users.noreply.github.com> Co-authored-by: Shivam Kumar <85393390+TinyTijil@users.noreply.github.com> Co-authored-by: Yanbo Wang Co-authored-by: Lisa Dziuba Co-authored-by: Karamoko Israël Abdelaziz Axel <72276211+karamokoisrael@users.noreply.github.com> Co-authored-by: duds Co-authored-by: Fabio Stabile <93452841+fabioS24@users.noreply.github.com> Co-authored-by: Fabio Stabile Co-authored-by: Naresh Thakur <122244033+thinklikeacto@users.noreply.github.com> Co-authored-by: Gustaf <79180496+GGyll@users.noreply.github.com> --- .../CreateRoadmap/CreateRoadmapModal.tsx | 19 +- .../CustomRoadmap/CustomRoadmap.tsx | 48 ++-- .../CustomRoadmap/RoadmapHeader.tsx | 224 +++++++++--------- .../CustomRoadmap/Showcase/ShowcaseAlert.tsx | 88 +++++++ .../CustomRoadmap/Showcase/ShowcaseStatus.tsx | 42 ++++ .../Showcase/SubmitShowcaseWarning.tsx | 122 ++++++++++ .../CustomRoadmap/SkeletonRoadmapHeader.tsx | 6 +- .../Dashboard/DashboardAiRoadmaps.tsx | 5 +- .../DiscoverRoadmaps/DiscoverRoadmaps.tsx | 16 -- .../GenerateRoadmap/PayToBypass.tsx | 8 +- src/hooks/use-custom-roadmap.ts | 41 ++++ src/lib/query-http.ts | 146 ++++++++++++ src/stores/query-client.ts | 11 + 13 files changed, 609 insertions(+), 167 deletions(-) create mode 100644 src/components/CustomRoadmap/Showcase/ShowcaseAlert.tsx create mode 100644 src/components/CustomRoadmap/Showcase/ShowcaseStatus.tsx create mode 100644 src/components/CustomRoadmap/Showcase/SubmitShowcaseWarning.tsx create mode 100644 src/hooks/use-custom-roadmap.ts create mode 100644 src/lib/query-http.ts create mode 100644 src/stores/query-client.ts diff --git a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx index d77a7dbbb..3b6672289 100644 --- a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx +++ b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx @@ -23,11 +23,16 @@ export const allowedCustomRoadmapType = ['role', 'skill'] as const; export type AllowedCustomRoadmapType = (typeof allowedCustomRoadmapType)[number]; -export const allowedShowcaseStatus = ['visible', 'hidden'] as const; +export const allowedShowcaseStatus = [ + 'submitted', + 'approved', + 'rejected', + 'rejected_with_reason', +] as const; export type AllowedShowcaseStatus = (typeof allowedShowcaseStatus)[number]; export interface RoadmapDocument { - _id?: string; + _id: string; title: string; description?: string; slug?: string; @@ -51,14 +56,22 @@ export interface RoadmapDocument { edges: any[]; isDiscoverable?: boolean; - showcaseStatus?: AllowedShowcaseStatus; ratings: { average: number; + totalCount: number; breakdown: { [key: number]: number; }; }; + showcaseStatus?: AllowedShowcaseStatus; + showcaseRejectedReason?: string; + showcaseRejectedAt?: Date; + showcaseSubmittedAt?: Date; + showcaseApprovedAt?: Date; + + hasMigratedContent?: boolean; + createdAt: Date; updatedAt: Date; } diff --git a/src/components/CustomRoadmap/CustomRoadmap.tsx b/src/components/CustomRoadmap/CustomRoadmap.tsx index f28e98545..c9e86684d 100644 --- a/src/components/CustomRoadmap/CustomRoadmap.tsx +++ b/src/components/CustomRoadmap/CustomRoadmap.tsx @@ -1,12 +1,15 @@ import { useEffect, useState } from 'react'; import { getUrlParams } from '../../lib/browser'; -import { type AppError, type FetchError, httpGet } from '../../lib/http'; import { RoadmapHeader } from './RoadmapHeader'; import { TopicDetail } from '../TopicDetail/TopicDetail'; import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal'; import { currentRoadmap } from '../../stores/roadmap'; import { RestrictedPage } from './RestrictedPage'; import { FlowRoadmapRenderer } from './FlowRoadmapRenderer'; +import { useQuery } from '@tanstack/react-query'; +import { queryClient } from '../../stores/query-client'; +import { httpGet, type FetchError } from '../../lib/query-http'; +import { useCustomRoadmap } from '../../hooks/use-custom-roadmap'; export const allowedLinkTypes = [ 'video', @@ -71,43 +74,24 @@ export function CustomRoadmap(props: CustomRoadmapProps) { const [isLoading, setIsLoading] = useState(true); const [roadmap, setRoadmap] = useState(null); - const [error, setError] = useState(); - async function getRoadmap() { - setIsLoading(true); + const { data, error } = useCustomRoadmap({ + id, + secret, + slug, + }); - const roadmapUrl = slug - ? new URL( - `${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap-by-slug/${slug}`, - ) - : new URL(`${import.meta.env.PUBLIC_API_URL}/v1-get-roadmap/${id}`); - - if (secret) { - roadmapUrl.searchParams.set('secret', secret); - } - - const { response, error } = await httpGet( - roadmapUrl.toString(), - ); - - if (error || !response) { - setError(error); - setIsLoading(false); + useEffect(() => { + if (!data) { return; } - document.title = `${response.title} - roadmap.sh`; - - setRoadmap(response); - currentRoadmap.set(response); + document.title = `${data.title} - roadmap.sh`; + setRoadmap(data); + currentRoadmap.set(data); setIsLoading(false); - } - - useEffect(() => { - getRoadmap().finally(() => { - hideRoadmapLoader(); - }); - }, []); + hideRoadmapLoader(); + }, [data]); if (isLoading) { return null; diff --git a/src/components/CustomRoadmap/RoadmapHeader.tsx b/src/components/CustomRoadmap/RoadmapHeader.tsx index 42dcde903..5775d89e3 100644 --- a/src/components/CustomRoadmap/RoadmapHeader.tsx +++ b/src/components/CustomRoadmap/RoadmapHeader.tsx @@ -11,6 +11,8 @@ import { RoadmapActionButton } from './RoadmapActionButton'; import { ShareRoadmapButton } from '../ShareRoadmapButton.tsx'; import { CustomRoadmapAlert } from './CustomRoadmapAlert.tsx'; import { CustomRoadmapRatings } from './CustomRoadmapRatings.tsx'; +import { ShowcaseStatus } from './Showcase/ShowcaseStatus.tsx'; +import { ShowcaseAlert } from './Showcase/ShowcaseAlert.tsx'; type RoadmapHeaderProps = {}; @@ -73,122 +75,132 @@ export function RoadmapHeader(props: RoadmapHeaderProps) { : '/images/default-avatar.png'; return ( -
-
- {!$canManageCurrentRoadmap && } - - {creator?.name && ( -
- {creator.name} - - Created by  - - {creator?.name} - - {team && ( - <> -  from  - - {team?.name} - - - )} - -
+ <> +
+ {$currentRoadmap && $canManageCurrentRoadmap && ( + )} -
-

{title}

-

- {description} -

-
-
-
- - ← -  Discover more - +
+ {!$canManageCurrentRoadmap && } - + {creator?.name && ( +
+ {creator.name} + + Created by  + + {creator?.name} + + {team && ( + <> +  from  + + {team?.name} + + + )} + +
+ )} +
+

{title}

+

+ {description} +

-
- {$canManageCurrentRoadmap && ( - <> - {isSharing && $currentRoadmap && ( - setIsSharing(false)} - onShareSettingsUpdate={(settings) => { - currentRoadmap.set({ - ...$currentRoadmap, - ...settings, - }); + +
+ +
+ {$canManageCurrentRoadmap && ( + <> + {isSharing && $currentRoadmap && ( + setIsSharing(false)} + onShareSettingsUpdate={(settings) => { + currentRoadmap.set({ + ...$currentRoadmap, + ...settings, + }); + }} + /> + )} + + {$currentRoadmap && ( + + )} + + setIsSharing(true)} + onCustomize={() => { + window.location.href = `${ + import.meta.env.PUBLIC_EDITOR_APP_URL + }/${$currentRoadmap?._id}`; + }} + onDelete={() => { + const confirmation = window.confirm( + 'Are you sure you want to delete this roadmap?', + ); + + if (!confirmation) { + return; + } + + deleteResource().finally(() => null); }} /> - )} + + )} - setIsSharing(true)} - onCustomize={() => { - window.location.href = `${ - import.meta.env.PUBLIC_EDITOR_APP_URL - }/${$currentRoadmap?._id}`; - }} - onDelete={() => { - const confirmation = window.confirm( - 'Are you sure you want to delete this roadmap?', - ); - - if (!confirmation) { - return; - } - - deleteResource().finally(() => null); - }} + {showcaseStatus === 'approved' && ( + - - )} - - {((ratings?.average || 0) > 0 || showcaseStatus === 'visible') && ( - - )} + )} +
-
- + +
-
+ ); } diff --git a/src/components/CustomRoadmap/Showcase/ShowcaseAlert.tsx b/src/components/CustomRoadmap/Showcase/ShowcaseAlert.tsx new file mode 100644 index 000000000..2f6933cb0 --- /dev/null +++ b/src/components/CustomRoadmap/Showcase/ShowcaseAlert.tsx @@ -0,0 +1,88 @@ +import { EyeIcon, FlagIcon, FrownIcon, SmileIcon } from 'lucide-react'; +import { cn } from '../../../lib/classname'; +import type { GetRoadmapResponse } from '../CustomRoadmap'; +import { useState } from 'react'; +import { SubmitShowcaseWarning } from './SubmitShowcaseWarning'; + +type ShowcaseAlertProps = { + currentRoadmap: GetRoadmapResponse; +}; + +export function ShowcaseAlert(props: ShowcaseAlertProps) { + const { currentRoadmap } = props; + + const [showRejectedReason, setShowRejectedReason] = useState(false); + + const { showcaseStatus } = currentRoadmap; + if (!showcaseStatus) { + return null; + } + + const showcaseStatusMap = { + submitted: { + icon: EyeIcon, + label: + 'We are currently reviewing your roadmap, please wait for our response.', + className: 'bg-blue-100 text-blue-600 border-blue-200', + }, + approved: { + icon: SmileIcon, + label: 'Hooray! Your roadmap is now visible on the community page.', + className: 'text-green-600 bg-green-100 border-green-300', + }, + rejected: { + icon: FrownIcon, + label: 'Sorry, we are unable to feature your roadmap at this time.', + className: 'text-red-600 bg-red-100 border-red-300', + }, + rejected_with_reason: { + icon: FlagIcon, + label: ( + <> + Your roadmap could not be featured at this time{' '} + + + ), + className: 'text-red-800 bg-red-200 border-red-200', + }, + }; + const showcaseStatusDetails = showcaseStatusMap[showcaseStatus]; + if (!showcaseStatusDetails) { + return null; + } + + const { icon: Icon, label, className } = showcaseStatusDetails; + + return ( + <> + {showRejectedReason && ( + { + setShowRejectedReason(false); + }} + /> + )} + +
+
+
+ +
{label}
+
+
+
+ + ); +} diff --git a/src/components/CustomRoadmap/Showcase/ShowcaseStatus.tsx b/src/components/CustomRoadmap/Showcase/ShowcaseStatus.tsx new file mode 100644 index 000000000..cfb84837b --- /dev/null +++ b/src/components/CustomRoadmap/Showcase/ShowcaseStatus.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { SubmitShowcaseWarning } from './SubmitShowcaseWarning'; +import type { GetRoadmapResponse } from '../CustomRoadmap'; +import { SendIcon } from 'lucide-react'; + +type ShowcaseStatusProps = { + currentRoadmap: GetRoadmapResponse; +}; + +export function ShowcaseStatus(props: ShowcaseStatusProps) { + const { currentRoadmap } = props; + + const { showcaseStatus } = currentRoadmap; + const [showSubmitWarning, setShowSubmitWarning] = useState(false); + + if (!currentRoadmap || showcaseStatus) { + return null; + } + + return ( + <> + {showSubmitWarning && ( + { + setShowSubmitWarning(false); + }} + /> + )} + + + + ); +} diff --git a/src/components/CustomRoadmap/Showcase/SubmitShowcaseWarning.tsx b/src/components/CustomRoadmap/Showcase/SubmitShowcaseWarning.tsx new file mode 100644 index 000000000..ddf8359af --- /dev/null +++ b/src/components/CustomRoadmap/Showcase/SubmitShowcaseWarning.tsx @@ -0,0 +1,122 @@ +import { useMutation } from '@tanstack/react-query'; +import { Modal } from '../../Modal'; +import { queryClient } from '../../../stores/query-client'; +import { httpPost } from '../../../lib/query-http'; +import { useStore } from '@nanostores/react'; +import { currentRoadmap } from '../../../stores/roadmap'; +import { useToast } from '../../../hooks/use-toast'; +import { DateTime } from 'luxon'; + +type SubmitShowcaseWarningProps = { + onClose: () => void; +}; + +export function SubmitShowcaseWarning(props: SubmitShowcaseWarningProps) { + const { onClose } = props; + + const toast = useToast(); + const $currentRoadmap = useStore(currentRoadmap); + + const submit = useMutation( + { + mutationFn: async () => { + return httpPost(`/v1-submit-for-showcase/${$currentRoadmap?._id}`, {}); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['get-roadmap'], + }); + + onClose(); + }, + onError: (error) => { + toast.error(error?.message || 'Something went wrong'); + }, + }, + queryClient, + ); + + const { + showcaseStatus, + showcaseRejectedReason, + showcaseRejectedAt, + updatedAt, + } = $currentRoadmap || {}; + + return ( + +
+

+ {showcaseStatus === 'rejected_with_reason' + ? 'Rejected with Reason' + : 'Feature Your Roadmap'} +

+

+ {showcaseStatus === 'rejected_with_reason' && ( + <> + + {showcaseRejectedReason} + + + Feel free to make changes to your roadmap and resubmit. + + + )} + {!showcaseStatus && ( + <> + We will review your roadmap and if accepted, we will make it + public and show it on the community roadmap listing.{' '} + + Are you sure to submit? + + + )} +

+ +
+ + +
+
+
+ ); +} diff --git a/src/components/CustomRoadmap/SkeletonRoadmapHeader.tsx b/src/components/CustomRoadmap/SkeletonRoadmapHeader.tsx index e0d9bd14d..16d7832ef 100644 --- a/src/components/CustomRoadmap/SkeletonRoadmapHeader.tsx +++ b/src/components/CustomRoadmap/SkeletonRoadmapHeader.tsx @@ -2,8 +2,8 @@ export function SkeletonRoadmapHeader() { return (
-
-
+
+
@@ -12,7 +12,7 @@ export function SkeletonRoadmapHeader() {
-
+
diff --git a/src/components/Dashboard/DashboardAiRoadmaps.tsx b/src/components/Dashboard/DashboardAiRoadmaps.tsx index 63539d52d..bcc6ca597 100644 --- a/src/components/Dashboard/DashboardAiRoadmaps.tsx +++ b/src/components/Dashboard/DashboardAiRoadmaps.tsx @@ -27,9 +27,7 @@ export function DashboardAiRoadmaps(props: DashboardAiRoadmapsProps) { return ( <>
-

- My AI Roadmaps -

+

My AI Roadmaps

{roadmaps.map((roadmap) => ( diff --git a/src/components/DiscoverRoadmaps/DiscoverRoadmaps.tsx b/src/components/DiscoverRoadmaps/DiscoverRoadmaps.tsx index 7cf290df4..f93b92b92 100644 --- a/src/components/DiscoverRoadmaps/DiscoverRoadmaps.tsx +++ b/src/components/DiscoverRoadmaps/DiscoverRoadmaps.tsx @@ -133,14 +133,6 @@ export function DiscoverRoadmaps(props: DiscoverRoadmapsProps) { @@ -131,7 +131,7 @@ export function PayToBypass(props: PayToBypassProps) {