parent
e39fadb032
commit
3ceab552f6
6 changed files with 206 additions and 5 deletions
@ -0,0 +1,50 @@ |
|||||||
|
import { getUser } from '../../lib/jwt'; |
||||||
|
import { getPercentage } from '../../helper/number'; |
||||||
|
import { ProjectProgressActions } from './ProjectProgressActions'; |
||||||
|
import { cn } from '../../lib/classname'; |
||||||
|
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions'; |
||||||
|
import { ProjectStatus } from './ProjectStatus'; |
||||||
|
import { ThumbsUp } from 'lucide-react'; |
||||||
|
|
||||||
|
type ProjectProgressType = { |
||||||
|
projectStatus: ProjectStatusDocument & { |
||||||
|
title: string; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function ProjectProgress(props: ProjectProgressType) { |
||||||
|
const { projectStatus } = props; |
||||||
|
const userId = getUser()?.id; |
||||||
|
|
||||||
|
const shouldShowActions = |
||||||
|
projectStatus.submittedAt && projectStatus.submittedAt !== null; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="relative"> |
||||||
|
<a |
||||||
|
className={cn( |
||||||
|
'group relative flex w-full items-center justify-between overflow-hidden rounded-md border border-gray-300 bg-white px-3 py-2 pr-7 text-left text-sm transition-all hover:border-gray-400', |
||||||
|
shouldShowActions ? '' : 'pr-3', |
||||||
|
)} |
||||||
|
href={`/projects/${projectStatus.projectId}`} |
||||||
|
target="_blank" |
||||||
|
> |
||||||
|
<ProjectStatus projectStatus={projectStatus} /> |
||||||
|
<span className="ml-2 flex-grow truncate">{projectStatus?.title}</span> |
||||||
|
<span className="inline-flex items-center gap-1 text-xs text-gray-400"> |
||||||
|
{projectStatus.upvotes} |
||||||
|
<ThumbsUp className="size-2.5 stroke-[2.5px]" /> |
||||||
|
</span> |
||||||
|
</a> |
||||||
|
|
||||||
|
{shouldShowActions && ( |
||||||
|
<div className="absolute right-2 top-0 flex h-full items-center"> |
||||||
|
<ProjectProgressActions |
||||||
|
userId={userId!} |
||||||
|
projectId={projectStatus.projectId} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
import { MoreVertical, X } from 'lucide-react'; |
||||||
|
import { useRef, useState } from 'react'; |
||||||
|
import { useOutsideClick } from '../../hooks/use-outside-click'; |
||||||
|
import { useKeydown } from '../../hooks/use-keydown'; |
||||||
|
import { cn } from '../../lib/classname'; |
||||||
|
import { useCopyText } from '../../hooks/use-copy-text'; |
||||||
|
import { CheckIcon } from '../ReactIcons/CheckIcon'; |
||||||
|
import { ShareIcon } from '../ReactIcons/ShareIcon'; |
||||||
|
|
||||||
|
type ProjectProgressActionsType = { |
||||||
|
userId: string; |
||||||
|
projectId: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export function ProjectProgressActions(props: ProjectProgressActionsType) { |
||||||
|
const { userId, projectId } = props; |
||||||
|
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null); |
||||||
|
const [isOpen, setIsOpen] = useState(false); |
||||||
|
|
||||||
|
const { copyText, isCopied } = useCopyText(); |
||||||
|
|
||||||
|
const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${userId}`; |
||||||
|
|
||||||
|
useOutsideClick(dropdownRef, () => { |
||||||
|
setIsOpen(false); |
||||||
|
}); |
||||||
|
|
||||||
|
useKeydown('Escape', () => { |
||||||
|
setIsOpen(false); |
||||||
|
}); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="relative h-full" ref={dropdownRef}> |
||||||
|
<button |
||||||
|
className="h-full text-gray-400 hover:text-gray-700" |
||||||
|
onClick={() => setIsOpen(!isOpen)} |
||||||
|
> |
||||||
|
<MoreVertical size={16} /> |
||||||
|
</button> |
||||||
|
|
||||||
|
{isOpen && ( |
||||||
|
<div className="absolute right-0 top-8 z-10 w-48 overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg"> |
||||||
|
<button |
||||||
|
className={cn( |
||||||
|
'flex w-full items-center gap-1.5 p-2 text-xs font-medium hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-70 sm:text-sm', |
||||||
|
isCopied ? 'text-green-500' : 'text-gray-500 hover:text-black', |
||||||
|
)} |
||||||
|
onClick={() => { |
||||||
|
copyText(projectSolutionUrl); |
||||||
|
}} |
||||||
|
> |
||||||
|
{isCopied ? ( |
||||||
|
<> |
||||||
|
<CheckIcon additionalClasses="h-3.5 w-3.5" /> Link Copied |
||||||
|
</> |
||||||
|
) : ( |
||||||
|
<> |
||||||
|
<ShareIcon className="h-3.5 w-3.5 stroke-[2.5px]" /> Share |
||||||
|
Solution |
||||||
|
</> |
||||||
|
)} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import { CircleDashed } from 'lucide-react'; |
||||||
|
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions'; |
||||||
|
import { CheckIcon } from '../ReactIcons/CheckIcon'; |
||||||
|
|
||||||
|
type ProjectStatusType = { |
||||||
|
projectStatus: ProjectStatusDocument & { |
||||||
|
title: string; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function ProjectStatus(props: ProjectStatusType) { |
||||||
|
const { projectStatus } = props; |
||||||
|
|
||||||
|
const { submittedAt, repositoryUrl } = projectStatus; |
||||||
|
const status = submittedAt && repositoryUrl ? 'submitted' : 'started'; |
||||||
|
|
||||||
|
if (status === 'submitted') { |
||||||
|
return <CheckIcon additionalClasses="size-3 text-gray-500 shrink-0" />; |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<CircleDashed className="size-3 shrink-0 stroke-[2.5px] text-gray-400" /> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue