parent
36707fac24
commit
c1e00a476e
4 changed files with 5 additions and 239 deletions
@ -1,222 +0,0 @@ |
||||
import { useEffect, useState } from 'react'; |
||||
import { cn } from '../../lib/classname'; |
||||
import { StartProjectModal } from './StartProjectModal'; |
||||
import { SubmitProjectModal } from './SubmitProjectModal'; |
||||
import { pageProgressMessage } from '../../stores/page'; |
||||
import { httpGet, httpPost } from '../../lib/http'; |
||||
import { useToast } from '../../hooks/use-toast'; |
||||
|
||||
type ProjectStatusResponse = { |
||||
id?: string; |
||||
|
||||
startedAt?: Date; |
||||
submittedAt?: Date; |
||||
repositoryUrl?: string; |
||||
|
||||
upvotes: number; |
||||
downvotes: number; |
||||
}; |
||||
|
||||
type ProjectMilestoneStripProps = { |
||||
projectId: string; |
||||
}; |
||||
|
||||
export function ProjectMilestoneStrip(props: ProjectMilestoneStripProps) { |
||||
const { projectId } = props; |
||||
|
||||
const toast = useToast(); |
||||
|
||||
const [isLoading, setIsLoading] = useState(true); |
||||
const [stepIndex, setStepIndex] = useState(0); |
||||
|
||||
const [projectStatus, setProjectStatus] = useState<ProjectStatusResponse>({ |
||||
upvotes: 0, |
||||
downvotes: 0, |
||||
}); |
||||
|
||||
const [isStartProjectModalOpen, setIsStartProjectModalOpen] = useState(false); |
||||
const [isSubmitProjectModalOpen, setIsSubmitProjectModalOpen] = |
||||
useState(false); |
||||
|
||||
const handleStartProject = async () => { |
||||
pageProgressMessage.set('Starting project...'); |
||||
setIsStartProjectModalOpen(true); |
||||
|
||||
const { response, error } = await httpPost<{ |
||||
startedAt: Date; |
||||
}>(`${import.meta.env.PUBLIC_API_URL}/v1-start-project/${projectId}`, {}); |
||||
|
||||
if (error || !response) { |
||||
toast.error(error?.message || 'Failed to start project'); |
||||
pageProgressMessage.set(''); |
||||
return; |
||||
} |
||||
|
||||
setStepIndex(1); |
||||
setProjectStatus({ |
||||
...projectStatus, |
||||
startedAt: response.startedAt, |
||||
}); |
||||
pageProgressMessage.set(''); |
||||
}; |
||||
|
||||
const loadProjectStatus = async () => { |
||||
setIsLoading(true); |
||||
|
||||
const { response, error } = await httpGet<ProjectStatusResponse>( |
||||
`${import.meta.env.PUBLIC_API_URL}/v1-project-status/${projectId}`, |
||||
{}, |
||||
); |
||||
|
||||
if (error || !response) { |
||||
toast.error(error?.message || 'Failed to load project status'); |
||||
setIsLoading(false); |
||||
return; |
||||
} |
||||
|
||||
const { startedAt, submittedAt, upvotes } = response; |
||||
|
||||
if (upvotes >= 10) { |
||||
setStepIndex(4); |
||||
} else if (upvotes >= 5) { |
||||
setStepIndex(3); |
||||
} else if (submittedAt) { |
||||
setStepIndex(2); |
||||
} else if (startedAt) { |
||||
setStepIndex(1); |
||||
} |
||||
|
||||
setProjectStatus(response); |
||||
setIsLoading(false); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
loadProjectStatus().finally(() => {}); |
||||
}, []); |
||||
|
||||
const startProjectModal = isStartProjectModalOpen ? ( |
||||
<StartProjectModal |
||||
onClose={() => setIsStartProjectModalOpen(false)} |
||||
startedAt={projectStatus?.startedAt} |
||||
/> |
||||
) : null; |
||||
const submitProjectModal = isSubmitProjectModalOpen ? ( |
||||
<SubmitProjectModal |
||||
onClose={() => setIsSubmitProjectModalOpen(false)} |
||||
projectId={projectId} |
||||
onSubmit={(response) => { |
||||
const { repositoryUrl, submittedAt } = response; |
||||
|
||||
setProjectStatus({ |
||||
...projectStatus, |
||||
repositoryUrl, |
||||
submittedAt, |
||||
}); |
||||
setStepIndex(2); |
||||
}} |
||||
repositoryUrl={projectStatus.repositoryUrl} |
||||
/> |
||||
) : null; |
||||
|
||||
return ( |
||||
<> |
||||
{startProjectModal} |
||||
{submitProjectModal} |
||||
|
||||
<div className="relative -mx-2 -mt-2 mb-5 overflow-hidden rounded-lg bg-gray-100/70 p-5"> |
||||
<div |
||||
className={cn( |
||||
'striped-loader absolute inset-0 z-10 bg-white', |
||||
!isLoading && 'hidden', |
||||
)} |
||||
/> |
||||
|
||||
<div className="grid grid-cols-4"> |
||||
<div className="flex flex-col"> |
||||
<MilestoneStep isActive={stepIndex >= 1} /> |
||||
|
||||
<button |
||||
className={cn( |
||||
'mt-3 inline-flex self-center text-center text-sm font-medium text-blue-600 underline underline-offset-2 hover:opacity-60', |
||||
stepIndex >= 1 && 'text-black no-underline', |
||||
)} |
||||
onClick={() => { |
||||
if (stepIndex < 1) { |
||||
handleStartProject().finally(() => {}); |
||||
} else { |
||||
setIsStartProjectModalOpen(true); |
||||
} |
||||
}} |
||||
> |
||||
Start Project |
||||
</button> |
||||
</div> |
||||
<div className="flex flex-col"> |
||||
<MilestoneStep isActive={stepIndex >= 2} /> |
||||
|
||||
<button |
||||
className={cn( |
||||
'mt-3 inline-flex self-center text-sm font-medium text-blue-600 underline underline-offset-2 hover:opacity-60', |
||||
stepIndex >= 2 && 'text-black no-underline', |
||||
stepIndex < 1 && 'text-black opacity-50 hover:opacity-50', |
||||
)} |
||||
onClick={() => setIsSubmitProjectModalOpen(true)} |
||||
disabled={stepIndex < 1} |
||||
> |
||||
{projectStatus?.repositoryUrl |
||||
? 'Update Solution' |
||||
: 'Submit Solution'} |
||||
</button> |
||||
</div> |
||||
<div className="flex flex-col"> |
||||
<MilestoneStep isActive={stepIndex >= 3} /> |
||||
|
||||
<span |
||||
className="mt-3 w-full text-center text-sm font-medium aria-disabled:opacity-50" |
||||
aria-disabled={stepIndex < 3} |
||||
> |
||||
5 Upvotes |
||||
</span> |
||||
</div> |
||||
<div className="flex flex-col"> |
||||
<MilestoneStep isActive={stepIndex >= 4} isLast={true} /> |
||||
|
||||
<span |
||||
className="mt-3 w-full text-center text-sm font-medium aria-disabled:opacity-50" |
||||
aria-disabled={stepIndex < 4} |
||||
> |
||||
10 Upvotes |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
type MilestoneStepProps = { |
||||
isActive: boolean; |
||||
isLast?: boolean; |
||||
}; |
||||
|
||||
function MilestoneStep(props: MilestoneStepProps) { |
||||
const { isActive = false, isLast = false } = props; |
||||
|
||||
return ( |
||||
<div |
||||
className={cn( |
||||
'relative h-1 w-full translate-x-1/2 bg-gray-300', |
||||
isActive && 'bg-gray-500', |
||||
isLast && 'bg-transparent', |
||||
)} |
||||
> |
||||
<span |
||||
className={cn( |
||||
'absolute -top-[4px] left-0 size-3 -translate-x-1/2 rounded-full border bg-white', |
||||
isActive && 'border-black bg-black', |
||||
isLast && '-translate-x-0', |
||||
)} |
||||
></span> |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue