pull/6513/head
Arik Chakma 4 months ago
parent 169a8f0356
commit 8db22e81b1
  1. 102
      src/components/Projects/ProjectMilestoneStrip.tsx
  2. 47
      src/components/Projects/StartProjectModal.tsx
  3. 136
      src/components/Projects/SubmitProjectModal.tsx
  4. 5
      src/layouts/BaseLayout.astro
  5. 34
      src/pages/projects/[projectId].astro

@ -0,0 +1,102 @@
import { useState } from 'react';
import { cn } from '../../lib/classname';
import { StartProjectModal } from './StartProjectModal';
import { SubmitProjectModal } from './SubmitProjectModal';
type ProjectMilestoneStripProps = {
projectId: string;
};
export function ProjectMilestoneStrip(props: ProjectMilestoneStripProps) {
const { projectId } = props;
const [stepIndex, setStepIndex] = useState(0);
const [isStartProjectModalOpen, setIsStartProjectModalOpen] = useState(false);
const [isSubmitProjectModalOpen, setIsSubmitProjectModalOpen] =
useState(false);
const startProjectModal = isStartProjectModalOpen ? (
<StartProjectModal onClose={() => setIsStartProjectModalOpen(false)} />
) : null;
const submitProjectModal = isSubmitProjectModalOpen ? (
<SubmitProjectModal
onClose={() => setIsSubmitProjectModalOpen(false)}
projectId={projectId}
onSubmit={() => setStepIndex(2)}
/>
) : null;
return (
<>
{startProjectModal}
{submitProjectModal}
<div className="relative -mx-2 -mt-2 mb-5 rounded-lg bg-gray-100/70 p-5">
<div className="grid grid-cols-4">
<div className="flex flex-col">
<MilestoneStep isActive={stepIndex === 1} />
<button
className="mt-3 text-left text-sm font-medium text-blue-600 underline underline-offset-2"
onClick={() => setIsStartProjectModalOpen(true)}
>
Start Project
</button>
</div>
<div className="flex flex-col">
<MilestoneStep isActive={stepIndex === 2} position="middle" />
<button
className="mt-3 text-sm font-medium text-blue-600 underline underline-offset-2"
onClick={() => setIsSubmitProjectModalOpen(true)}
>
Submit Solution
</button>
</div>
<div className="flex flex-col">
<MilestoneStep isActive={stepIndex === 2} position="middle" />
<span className="mt-3 w-full text-center text-sm font-medium">
Get 5 Likes
</span>
</div>
<div className="flex flex-col">
<MilestoneStep isActive={stepIndex === 2} position="end" />
<span className="mt-3 w-full text-right text-sm font-medium">
Get 10 Likes
</span>
</div>
</div>
</div>
</>
);
}
type MilestoneStepProps = {
isActive: boolean;
position?: 'start' | 'middle' | 'end';
};
function MilestoneStep(props: MilestoneStepProps) {
const { isActive = false, position = 'start' } = props;
return (
<div
className={cn(
'relative h-1 w-full bg-gray-300',
isActive && 'bg-gray-500',
)}
>
<span
className={cn(
'absolute -top-[4px] size-3 -translate-x-1/2 rounded-full border bg-white',
isActive && 'border-black bg-black',
position === 'start' && 'left-0',
position === 'middle' && 'left-1/2',
position === 'end' && 'right-0 translate-x-1/2',
)}
></span>
</div>
);
}

@ -0,0 +1,47 @@
import { X } from 'lucide-react';
import { Modal } from '../Modal';
type StartProjectModalProps = {
onClose: () => void;
};
export function StartProjectModal(props: StartProjectModalProps) {
const { onClose } = props;
const projectTips = [
'Create a repository on GitHub',
'Develop the required functionality',
'Add a readme and make sure to link to the project page',
'Once you are done, make sure to come back and submit your solution to get feedback from others.',
'Feel free to join our discord and ask for help if you get stuck.',
];
return (
<Modal onClose={onClose} bodyClassName="h-auto p-4">
<h2 className="mb-0.5 text-xl font-semibold">Started working...</h2>
<p className="text-balance text-sm text-gray-500">
You have started working on the project. Here are some tips to get most
out of it.
</p>
<ul className="ml-4 mt-4 list-disc space-y-1.5 marker:text-gray-400">
{projectTips.map((tip) => {
return (
<li key={tip} className="text-balance">
{tip}
</li>
);
})}
</ul>
<p className="mt-4 font-medium">Happy coding!</p>
<button
className="absolute right-2.5 top-2.5 text-gray-600 hover:text-black"
onClick={onClose}
>
<X className="h-5 w-5" />
</button>
</Modal>
);
}

@ -0,0 +1,136 @@
import { X } from 'lucide-react';
import { Modal } from '../Modal';
import { useState, type FormEvent } from 'react';
import { useToast } from '../../hooks/use-toast';
type SubmitProjectModalProps = {
onClose: () => void;
projectId: string;
onSubmit: () => void;
};
export function SubmitProjectModal(props: SubmitProjectModalProps) {
const { onClose, projectId, onSubmit } = props;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [repoUrl, setRepoUrl] = useState('');
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
setIsLoading(true);
setError('');
setSuccessMessage('');
const repoUrlParts = repoUrl
.replace(/https?:\/\/(www\.)?github\.com/, '')
.split('/');
const username = repoUrlParts[1];
const repoName = repoUrlParts[2];
if (!username || !repoName) {
throw new Error('Invalid GitHub repository URL');
}
const mainApiUrl = `https://api.github.com/repos/${username}/${repoName}`;
const allContentsUrl = `${mainApiUrl}/contents`;
const allContentsResponse = await fetch(allContentsUrl);
if (!allContentsResponse.ok) {
const errorData = await allContentsResponse.json();
if (errorData?.status === 404) {
throw new Error('Repository not found');
}
throw new Error('Failed to fetch repository contents');
}
const allContentsData = await allContentsResponse.json();
if (!Array.isArray(allContentsData)) {
throw new Error('Failed to fetch repository contents');
}
const readmeFile = allContentsData.find(
(file) => file.name.toLowerCase() === 'readme.md',
);
if (!readmeFile || !readmeFile.url) {
throw new Error('Readme file not found');
}
const readmeUrl = readmeFile.url;
const response = await fetch(readmeUrl);
if (!response.ok || response.status === 404) {
throw new Error('Readme file not found');
}
const data = await response.json();
if (!data.content) {
throw new Error('Readme file not found');
}
const readmeContent = window.atob(data.content);
const projectUrl = `${window.location.origin}/projects/${projectId}`;
if (!readmeContent.includes(projectUrl)) {
throw new Error('Project URL not found in the readme file');
}
// TODO: Make API call to update the project status
setSuccessMessage('Repository verified successfully');
setIsLoading(false);
onSubmit();
} catch (error: any) {
console.error(error);
setError(error?.message || 'Failed to verify repository');
setIsLoading(false);
}
};
return (
<Modal onClose={onClose} bodyClassName="h-auto p-4">
<h2 className="mb-0.5 text-xl font-semibold">
Submit Link to the GitHub repository
</h2>
<p className="text-balance text-sm text-gray-500">
Make sure to have a readme file in your project repository containing
the link to this project page.
</p>
<form className="mt-4" onSubmit={handleSubmit}>
<input
type="text"
className="w-full rounded-lg border border-gray-300 p-2 focus:border-gray-500 focus:outline-none"
placeholder="https://github.com/kamranahmedse/developer-roadmap"
value={repoUrl}
onChange={(e) => setRepoUrl(e.target.value)}
/>
{error && (
<p className="mt-2 text-sm font-medium text-red-500">{error}</p>
)}
{successMessage && (
<p className="mt-2 text-sm font-medium text-green-500">
{successMessage}
</p>
)}
<button
type="submit"
className="mt-2 w-full rounded-lg bg-black p-2 font-medium text-white disabled:opacity-50"
disabled={isLoading}
>
{isLoading ? 'Verifying...' : 'Verify'}
</button>
</form>
<button
className="absolute right-2.5 top-2.5 text-gray-600 hover:text-black"
onClick={onClose}
>
<X className="h-5 w-5" />
</button>
</Modal>
);
}

@ -109,7 +109,10 @@ const gaPageIdentifier = Astro.url.pathname
/> />
<meta name='apple-mobile-web-app-title' content='roadmap.sh' /> <meta name='apple-mobile-web-app-title' content='roadmap.sh' />
<meta name='application-name' content='roadmap.sh' /> <meta name='application-name' content='roadmap.sh' />
<meta name="ahrefs-site-verification" content="04588b1b3d0118b4f973fa24f9df38ca6300d152cc26529a639e9a34d09c9880"> <meta
name='ahrefs-site-verification'
content='04588b1b3d0118b4f973fa24f9df38ca6300d152cc26529a639e9a34d09c9880'
/>
<link <link
rel='apple-touch-icon' rel='apple-touch-icon'

@ -1,31 +1,13 @@
--- ---
import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro';
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro';
import { TopicDetail } from '../../components/TopicDetail/TopicDetail';
import { UserProgressModal } from '../../components/UserProgress/UserProgressModal';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { Badge } from '../../components/Badge'; import { Badge } from '../../components/Badge';
import {
generateArticleSchema,
generateFAQSchema,
} from '../../lib/jsonld-schema';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import RoadmapNote from '../../components/RoadmapNote.astro';
import { RoadmapTitleQuestion } from '../../components/RoadmapTitleQuestion';
import ResourceProgressStats from '../../components/ResourceProgressStats.astro';
import { import {
getAllProjects, getAllProjects,
getProjectById, getProjectById,
getProjectsByRoadmapId, type ProjectFrontmatter,
ProjectFrontmatter,
} from '../../lib/project'; } from '../../lib/project';
import AstroIcon from '../../components/AstroIcon.astro'; import AstroIcon from '../../components/AstroIcon.astro';
import MarkdownFile from '../../components/MarkdownFile.astro'; import { ProjectMilestoneStrip } from '../../components/Projects/ProjectMilestoneStrip';
import Github from '../github.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const projects = await getAllProjects(); const projects = await getAllProjects();
@ -46,7 +28,7 @@ const { projectId } = Astro.params as Params;
const project = await getProjectById(projectId); const project = await getProjectById(projectId);
const projectData = project.frontmatter as ProjectFrontmatter; const projectData = project.frontmatter as ProjectFrontmatter;
let jsonLdSchema = []; let jsonLdSchema: any[] = [];
const ogImageUrl = projectData?.seo?.ogImageUrl || '/images/og-img.png'; const ogImageUrl = projectData?.seo?.ogImageUrl || '/images/og-img.png';
const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/projects/${projectId}.md`; const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/projects/${projectId}.md`;
@ -66,14 +48,14 @@ const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/maste
<div class='bg-gray-50'> <div class='bg-gray-50'>
<div class='container'> <div class='container'>
<div <div
class='my-3 flex flex-wrap flex-row items-center gap-1.5 rounded-md border bg-white px-2 py-2 text-sm' class='my-3 flex flex-row flex-wrap items-center gap-1.5 rounded-md border bg-white px-2 py-2 text-sm'
> >
<AstroIcon icon='map' class='h-4 w-4' /> <AstroIcon icon='map' class='h-4 w-4' />
Relevant roadmaps <span class='flex flex-row flex-wrap gap-1'> Relevant roadmaps <span class='flex flex-row flex-wrap gap-1'>
{ {
project.roadmaps.map((roadmap) => ( project.roadmaps.map((roadmap) => (
<a <a
class='bg-gray-500 text-white text-sm px-1.5 rounded hover:bg-black transition-colors' class='rounded bg-gray-500 px-1.5 text-sm text-white transition-colors hover:bg-black'
href={`/${roadmap.id}`} href={`/${roadmap.id}`}
> >
{roadmap.frontmatter?.briefTitle} {roadmap.frontmatter?.briefTitle}
@ -94,7 +76,7 @@ const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/maste
</div> </div>
<div class='mt-4'> <div class='mt-4'>
<div class='flex flex-row gap-1.5 flex-wrap'> <div class='flex flex-row flex-wrap gap-1.5'>
{ {
projectData.skills.map((skill) => ( projectData.skills.map((skill) => (
<Badge variant='green' text={skill} /> <Badge variant='green' text={skill} />
@ -104,8 +86,10 @@ const githubUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/maste
</div> </div>
</div> </div>
<ProjectMilestoneStrip projectId={projectId} client:load />
<div <div
class='prose max-w-full prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-h2:mb-3 prose-h2:mt-5 prose-h3:mb-1 prose-h3:mt-5 prose-p:mb-2 prose-pre:my-3 prose-ul:my-3.5 prose-hr:my-5 [&>ul>li]:my-1' class='prose max-w-full prose-h2:mb-3 prose-h2:mt-5 prose-h3:mb-1 prose-h3:mt-5 prose-p:mb-2 prose-blockquote:font-normal prose-blockquote:text-gray-500 prose-pre:my-3 prose-ul:my-3.5 prose-hr:my-5 [&>ul>li]:my-1'
> >
<project.Content /> <project.Content />
</div> </div>

Loading…
Cancel
Save