computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.2 KiB
257 lines
7.2 KiB
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; |
|
import { useKeydown } from '../../hooks/use-keydown'; |
|
import { useOutsideClick } from '../../hooks/use-outside-click'; |
|
import DownIcon from '../../icons/down.svg'; |
|
import SpinnerIcon from '../../icons/spinner.svg'; |
|
import { isLoggedIn } from '../../lib/jwt'; |
|
import { |
|
ResourceProgressType, |
|
ResourceType, |
|
getTopicStatus, |
|
renderTopicProgress, |
|
updateResourceProgress, |
|
} from '../../lib/resource-progress'; |
|
|
|
type TopicProgressButtonProps = { |
|
topicId: string; |
|
resourceId: string; |
|
resourceType: ResourceType; |
|
|
|
onShowLoginPopup: () => void; |
|
onClose: () => void; |
|
}; |
|
|
|
const statusColors: Record<ResourceProgressType, string> = { |
|
done: 'bg-green-500', |
|
learning: 'bg-yellow-500', |
|
pending: 'bg-gray-300', |
|
skipped: 'bg-black', |
|
}; |
|
|
|
export function TopicProgressButton(props: TopicProgressButtonProps) { |
|
const { topicId, resourceId, resourceType, onClose, onShowLoginPopup } = |
|
props; |
|
|
|
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true); |
|
const [progress, setProgress] = useState<ResourceProgressType>('pending'); |
|
const [showChangeStatus, setShowChangeStatus] = useState(false); |
|
|
|
const changeStatusRef = useRef<HTMLDivElement>(null); |
|
|
|
useOutsideClick(changeStatusRef, () => { |
|
setShowChangeStatus(false); |
|
}); |
|
|
|
const isGuest = useMemo(() => !isLoggedIn(), []); |
|
|
|
useEffect(() => { |
|
if (!topicId || !resourceId || !resourceType) { |
|
return; |
|
} |
|
|
|
setIsUpdatingProgress(true); |
|
getTopicStatus({ topicId, resourceId, resourceType }) |
|
.then((status) => { |
|
setIsUpdatingProgress(false); |
|
setProgress(status); |
|
}) |
|
.catch(console.error); |
|
}, [topicId, resourceId, resourceType]); |
|
|
|
// Mark as done |
|
useKeydown( |
|
'd', |
|
() => { |
|
if (progress === 'done') { |
|
onClose(); |
|
return; |
|
} |
|
|
|
handleUpdateResourceProgress('done'); |
|
}, |
|
[progress] |
|
); |
|
|
|
// Mark as learning |
|
useKeydown( |
|
'l', |
|
() => { |
|
if (progress === 'learning') { |
|
onClose(); |
|
return; |
|
} |
|
|
|
handleUpdateResourceProgress('learning'); |
|
}, |
|
[progress] |
|
); |
|
|
|
// Mark as learning |
|
useKeydown( |
|
's', |
|
() => { |
|
if (progress === 'skipped') { |
|
onClose(); |
|
return; |
|
} |
|
|
|
handleUpdateResourceProgress('skipped'); |
|
}, |
|
[progress] |
|
); |
|
|
|
// Mark as pending |
|
useKeydown( |
|
'r', |
|
() => { |
|
console.log(progress); |
|
if (progress === 'pending') { |
|
onClose(); |
|
return; |
|
} |
|
|
|
handleUpdateResourceProgress('pending'); |
|
}, |
|
[progress] |
|
); |
|
|
|
const handleUpdateResourceProgress = (progress: ResourceProgressType) => { |
|
if (isGuest) { |
|
onClose(); |
|
onShowLoginPopup(); |
|
return; |
|
} |
|
|
|
setIsUpdatingProgress(true); |
|
updateResourceProgress( |
|
{ |
|
topicId, |
|
resourceId, |
|
resourceType, |
|
}, |
|
progress |
|
) |
|
.then(() => { |
|
setProgress(progress); |
|
onClose(); |
|
renderTopicProgress(topicId, progress); |
|
}) |
|
.catch((err) => { |
|
alert(err.message); |
|
console.error(err); |
|
}) |
|
.finally(() => { |
|
setIsUpdatingProgress(false); |
|
}); |
|
}; |
|
|
|
const allowMarkingSkipped = ['pending', 'learning', 'done'].includes( |
|
progress |
|
); |
|
const allowMarkingDone = ['skipped', 'pending', 'learning'].includes( |
|
progress |
|
); |
|
const allowMarkingLearning = ['done', 'skipped', 'pending'].includes( |
|
progress |
|
); |
|
const allowMarkingPending = ['skipped', 'done', 'learning'].includes( |
|
progress |
|
); |
|
|
|
if (isUpdatingProgress) { |
|
return ( |
|
<button className="inline-flex cursor-default items-center rounded-md border border-gray-300 bg-white p-1 px-2 text-sm text-black"> |
|
<img alt="Check" class="h-4 w-4 animate-spin" src={SpinnerIcon} /> |
|
<span className="ml-2">Updating Status..</span> |
|
</button> |
|
); |
|
} |
|
|
|
return ( |
|
<div className="relative inline-flex rounded-md border border-gray-300"> |
|
<span className="inline-flex cursor-default items-center p-1 px-2 text-sm text-black"> |
|
<span class="flex h-2 w-2"> |
|
<span |
|
class={`relative inline-flex h-2 w-2 rounded-full ${statusColors[progress]}`} |
|
></span> |
|
</span> |
|
<span className="ml-2 capitalize"> |
|
{progress === 'learning' ? 'In Progress' : progress} |
|
</span> |
|
</span> |
|
|
|
<button |
|
className="inline-flex cursor-pointer items-center rounded-br-md rounded-tr-md border-l border-l-gray-300 bg-gray-100 p-1 px-2 text-sm text-black hover:bg-gray-200" |
|
onClick={() => setShowChangeStatus(true)} |
|
> |
|
<span className="mr-0.5">Update Status</span> |
|
<img alt="Check" class="h-4 w-4" src={DownIcon} /> |
|
</button> |
|
|
|
{showChangeStatus && ( |
|
<div |
|
className="absolute right-0 top-full mt-1 flex min-w-[160px] flex-col divide-y rounded-md border border-gray-200 bg-white shadow-md [&>button:first-child]:rounded-t-md [&>button:last-child]:rounded-b-md" |
|
ref={changeStatusRef!} |
|
> |
|
{allowMarkingDone && ( |
|
<button |
|
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100" |
|
onClick={() => handleUpdateResourceProgress('done')} |
|
> |
|
<span> |
|
<span |
|
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['done']}`} |
|
></span> |
|
Done |
|
</span> |
|
<span class="text-xs text-gray-500">D</span> |
|
</button> |
|
)} |
|
{allowMarkingLearning && ( |
|
<button |
|
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100" |
|
onClick={() => handleUpdateResourceProgress('learning')} |
|
> |
|
<span> |
|
<span |
|
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['learning']}`} |
|
></span> |
|
In Progress |
|
</span> |
|
|
|
<span class="text-xs text-gray-500">L</span> |
|
</button> |
|
)} |
|
{allowMarkingPending && ( |
|
<button |
|
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100" |
|
onClick={() => handleUpdateResourceProgress('pending')} |
|
> |
|
<span> |
|
<span |
|
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['pending']}`} |
|
></span> |
|
Reset |
|
</span> |
|
<span class="text-xs text-gray-500">R</span> |
|
</button> |
|
)} |
|
{allowMarkingSkipped && ( |
|
<button |
|
class="inline-flex justify-between px-3 py-1.5 text-left text-sm text-gray-800 hover:bg-gray-100" |
|
onClick={() => handleUpdateResourceProgress('skipped')} |
|
> |
|
<span> |
|
<span |
|
class={`mr-2 inline-block h-2 w-2 rounded-full ${statusColors['skipped']}`} |
|
></span> |
|
Skip |
|
</span> |
|
<span class="text-xs text-gray-500">S</span> |
|
</button> |
|
)} |
|
</div> |
|
)} |
|
</div> |
|
); |
|
}
|
|
|