Allow creating personal version of frontend roadmap (#4627)

* Create Roadmap Version

* Change button position

* Update frontend JSON

* Remove `topicCount`

* Add fork at title

* Update UI for create your own version

* Add functionality to load your own version

* Load user version of roadmap

* Update forkable roadmap

---------

Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
pull/4631/head
Arik Chakma 1 year ago committed by GitHub
parent 7f6a42a0c5
commit 0558957673
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 147
      src/components/CreateVersion/CreateVersion.tsx
  2. 16
      src/components/CustomRoadmap/PersonalRoadmapList.tsx
  3. 15
      src/components/RoadmapHeader.astro
  4. 5743
      src/data/roadmaps/frontend/frontend-forkable.json
  5. 1
      src/data/roadmaps/frontend/frontend.md
  6. 1
      src/lib/roadmap.ts
  7. 1
      src/pages/[roadmapId]/index.astro

@ -0,0 +1,147 @@
import { useEffect, useState } from 'react';
import { httpGet, httpPost } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { isLoggedIn } from '../../lib/jwt';
import { GitFork, Layers2, Loader2, Map } from 'lucide-react';
import { showLoginPopup } from '../../lib/popup';
import type { RoadmapDocument } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
type CreateVersionProps = {
roadmapId: string;
};
export function CreateVersion(props: CreateVersionProps) {
const { roadmapId } = props;
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
const [isCreating, setIsCreating] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const [userVersion, setUserVersion] = useState<RoadmapDocument>();
async function loadMyVersion() {
if (!isLoggedIn()) {
return;
}
setIsLoading(true);
const { response, error } = await httpGet<RoadmapDocument>(
`${import.meta.env.PUBLIC_API_URL}/v1-get-my-version/${roadmapId}`,
{},
);
if (error || !response) {
setIsLoading(false);
return;
}
setIsLoading(false);
setUserVersion(response);
}
useEffect(() => {
loadMyVersion().finally(() => {
setIsLoading(false);
});
}, []);
async function createVersion() {
if (isCreating || !roadmapId) {
return;
}
if (!isLoggedIn()) {
showLoginPopup();
return;
}
setIsCreating(true);
const { response, error } = await httpPost<{ roadmapId: string }>(
`${import.meta.env.PUBLIC_API_URL}/v1-create-version/${roadmapId}`,
{},
);
if (error || !response) {
setIsCreating(false);
toast.error(error?.message || 'Failed to create version');
return;
}
const roadmapEditorUrl = `${
import.meta.env.PUBLIC_EDITOR_APP_URL
}/${response?.roadmapId}`;
window.open(roadmapEditorUrl, '_blank');
}
if (isLoading) {
return (
<div className="h-[30px] w-[206px] animate-pulse rounded-md bg-gray-300"></div>
);
}
if (!isLoading && userVersion?._id) {
return (
<div className={'flex items-center'}>
<a
href={`/r?id=${userVersion._id}`}
className="flex items-center rounded-md border border-blue-400 bg-gray-50 px-2.5 py-1 text-xs font-medium text-blue-600 hover:bg-blue-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:hover:bg-gray-100 max-sm:hidden sm:text-sm"
>
<Map size="15px" className="mr-1.5" />
Visit your own version
</a>
</div>
);
}
if (isConfirming) {
return (
<p className="flex h-[30px] items-center text-sm text-red-500">
Create and edit a custom roadmap from this?
<button
onClick={() => {
setIsConfirming(false);
createVersion().finally(() => null);
}}
className="ml-2 font-semibold underline underline-offset-2"
>
Yes
</button>
<span className="text-xs">&nbsp;/&nbsp;</span>
<button
className="font-semibold underline underline-offset-2"
onClick={() => setIsConfirming(false)}
>
No
</button>
</p>
);
}
return (
<button
disabled={isCreating}
className="flex items-center justify-center rounded-md border border-gray-300 bg-gray-50 px-2.5 py-1 text-xs font-medium text-black hover:bg-gray-200 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:hover:bg-gray-100 max-sm:hidden sm:text-sm"
onClick={() => {
if (!isLoggedIn()) {
showLoginPopup();
return;
}
setIsConfirming(true);
}}
>
{isCreating ? (
<>
<Loader2 className="mr-2 h-3 w-3 animate-spin stroke-[2.5]" />
Please wait ..
</>
) : (
<>
<GitFork className="mr-1.5" size="16px" />
Create your own Version
</>
)}
</button>
);
}

@ -176,24 +176,24 @@ function CustomRoadmapItem(props: CustomRoadmapItemProps) {
/> />
<a <a
href={`/r?id=${roadmap._id}`} href={editorLink}
className={ className={
'ml-2 flex items-center gap-2 rounded-md border border-gray-300 bg-white px-2 py-1.5 text-xs hover:bg-gray-50 focus:outline-none' 'ml-2 flex items-center gap-2 rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-xs text-black hover:bg-gray-50 focus:outline-none'
} }
target={'_blank'} target={'_blank'}
> >
<ExternalLink className="inline-block h-4 w-4" /> <PenSquare className="inline-block h-4 w-4" />
Visit Edit
</a> </a>
<a <a
href={editorLink} href={`/r?id=${roadmap._id}`}
className={ className={
'ml-2 flex items-center gap-2 rounded-md border border-gray-800 bg-gray-900 px-2.5 py-1.5 text-xs text-white hover:bg-gray-800 focus:outline-none' 'ml-2 flex items-center gap-2 rounded-md border border-blue-400 bg-white px-2 py-1.5 text-xs hover:bg-blue-50 focus:outline-none text-blue-600'
} }
target={'_blank'} target={'_blank'}
> >
<PenSquare className="inline-block h-4 w-4" /> <ExternalLink className="inline-block h-4 w-4" />
Edit Visit
</a> </a>
</div> </div>
</li> </li>

@ -8,7 +8,8 @@ import YouTubeAlert from './YouTubeAlert.astro';
import ProgressHelpPopup from './ProgressHelpPopup.astro'; import ProgressHelpPopup from './ProgressHelpPopup.astro';
import { MarkFavorite } from './FeaturedItems/MarkFavorite'; import { MarkFavorite } from './FeaturedItems/MarkFavorite';
import { TeamVersions } from './TeamVersions/TeamVersions'; import { TeamVersions } from './TeamVersions/TeamVersions';
import { RoadmapFrontmatter } from '../lib/roadmap'; import { CreateVersion } from './CreateVersion/CreateVersion';
import { type RoadmapFrontmatter } from '../lib/roadmap';
export interface Props { export interface Props {
title: string; title: string;
@ -20,6 +21,7 @@ export interface Props {
hasSearch?: boolean; hasSearch?: boolean;
question?: RoadmapFrontmatter['question']; question?: RoadmapFrontmatter['question'];
hasTopics?: boolean; hasTopics?: boolean;
isForkable?: boolean;
} }
const { const {
@ -32,6 +34,7 @@ const {
note, note,
hasTopics = false, hasTopics = false,
question, question,
isForkable = false,
} = Astro.props; } = Astro.props;
const isRoadmapReady = !isUpcoming; const isRoadmapReady = !isUpcoming;
@ -58,13 +61,21 @@ const hasTnsBanner = !!tnsBannerLink;
]} ]}
> >
<div class='mb-3 mt-0 sm:mb-4'> <div class='mb-3 mt-0 sm:mb-4'>
{
isForkable && (
<div class='mb-2'>
<CreateVersion client:load roadmapId={roadmapId} />
</div>
)
}
<h1 class='mb-0.5 text-2xl font-bold sm:mb-2 sm:text-4xl'> <h1 class='mb-0.5 text-2xl font-bold sm:mb-2 sm:text-4xl'>
{title} {title}
<span class='relative top-0 sm:-top-1'> <span class='relative top-0 sm:-top-1'>
<MarkFavorite <MarkFavorite
resourceId={roadmapId} resourceId={roadmapId}
resourceType='roadmap' resourceType='roadmap'
className='text-gray-500 !opacity-100 hover:text-gray-600 [&>svg]:stroke-[0.4] [&>svg]:stroke-gray-400 hover:[&>svg]:stroke-gray-600 [&>svg]:h-4 [&>svg]:w-4 sm:[&>svg]:h-4 sm:[&>svg]:w-4 ml-1.5 relative focus:outline-0' className='relative ml-1.5 text-gray-500 !opacity-100 hover:text-gray-600 focus:outline-0 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:stroke-gray-400 [&>svg]:stroke-[0.4] hover:[&>svg]:stroke-gray-600 sm:[&>svg]:h-4 sm:[&>svg]:w-4'
client:only='react' client:only='react'
/> />
</span> </span>

File diff suppressed because it is too large Load Diff

@ -7,6 +7,7 @@ briefDescription: 'Step by step guide to becoming a frontend developer in 2023'
title: 'Frontend Developer' title: 'Frontend Developer'
description: 'Step by step guide to becoming a modern frontend developer in 2023' description: 'Step by step guide to becoming a modern frontend developer in 2023'
hasTopics: true hasTopics: true
isForkable: true
tnsBannerLink: 'https://thenewstack.io?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Alert' tnsBannerLink: 'https://thenewstack.io?utm_source=roadmap.sh&utm_medium=Referral&utm_campaign=Alert'
question: question:
title: 'What is Frontend Development?' title: 'What is Frontend Development?'

@ -8,6 +8,7 @@ export interface RoadmapFrontmatter {
title: string; title: string;
description: string; description: string;
hasTopics: boolean; hasTopics: boolean;
isForkable: boolean;
isNew: boolean; isNew: boolean;
isUpcoming: boolean; isUpcoming: boolean;
tnsBannerLink?: string; tnsBannerLink?: string;

@ -86,6 +86,7 @@ if (roadmapFAQs.length) {
roadmapId={roadmapId} roadmapId={roadmapId}
hasTopics={roadmapData.hasTopics} hasTopics={roadmapData.hasTopics}
isUpcoming={roadmapData.isUpcoming} isUpcoming={roadmapData.isUpcoming}
isForkable={roadmapData.isForkable}
question={roadmapData.question} question={roadmapData.question}
/> />

Loading…
Cancel
Save