parent
169a8f0356
commit
8db22e81b1
5 changed files with 298 additions and 26 deletions
@ -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> |
||||
); |
||||
} |
Loading…
Reference in new issue