mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-22 17:02:58 +02:00
Refactor progress button
This commit is contained in:
@@ -1,8 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
import { useMemo, useRef, useState } from 'preact/hooks';
|
||||||
import CheckIcon from '../../icons/check.svg';
|
|
||||||
import CloseIcon from '../../icons/close.svg';
|
import CloseIcon from '../../icons/close.svg';
|
||||||
import ProgressIcon from '../../icons/progress.svg';
|
|
||||||
import ResetIcon from '../../icons/reset.svg';
|
|
||||||
import SpinnerIcon from '../../icons/spinner.svg';
|
import SpinnerIcon from '../../icons/spinner.svg';
|
||||||
|
|
||||||
import { useKeydown } from '../../hooks/use-keydown';
|
import { useKeydown } from '../../hooks/use-keydown';
|
||||||
@@ -12,14 +9,13 @@ import { useToggleTopic } from '../../hooks/use-toggle-topic';
|
|||||||
import { httpGet } from '../../lib/http';
|
import { httpGet } from '../../lib/http';
|
||||||
import { isLoggedIn } from '../../lib/jwt';
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
import {
|
import {
|
||||||
getTopicStatus,
|
|
||||||
isTopicDone,
|
isTopicDone,
|
||||||
renderTopicProgress,
|
renderTopicProgress,
|
||||||
ResourceProgressType,
|
|
||||||
ResourceType,
|
ResourceType,
|
||||||
updateResourceProgress as updateResourceProgressApi,
|
updateResourceProgress as updateResourceProgressApi,
|
||||||
} from '../../lib/resource-progress';
|
} from '../../lib/resource-progress';
|
||||||
import { pageLoadingMessage, sponsorHidden } from '../../stores/page';
|
import { pageLoadingMessage, sponsorHidden } from '../../stores/page';
|
||||||
|
import { TopicProgressButton } from './TopicProgressButton';
|
||||||
|
|
||||||
export function TopicDetail() {
|
export function TopicDetail() {
|
||||||
const [isActive, setIsActive] = useState(false);
|
const [isActive, setIsActive] = useState(false);
|
||||||
@@ -27,9 +23,6 @@ export function TopicDetail() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [topicHtml, setTopicHtml] = useState('');
|
const [topicHtml, setTopicHtml] = useState('');
|
||||||
|
|
||||||
const [progress, setProgress] = useState<ResourceProgressType>('pending');
|
|
||||||
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true);
|
|
||||||
|
|
||||||
const isGuest = useMemo(() => !isLoggedIn(), []);
|
const isGuest = useMemo(() => !isLoggedIn(), []);
|
||||||
const topicRef = useRef<HTMLDivElement>(null);
|
const topicRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -52,45 +45,6 @@ export function TopicDetail() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
|
|
||||||
setIsUpdatingProgress(true);
|
|
||||||
updateResourceProgressApi(
|
|
||||||
{
|
|
||||||
topicId,
|
|
||||||
resourceId,
|
|
||||||
resourceType,
|
|
||||||
},
|
|
||||||
progress
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
setProgress(progress);
|
|
||||||
setIsActive(false);
|
|
||||||
renderTopicProgress(topicId, progress);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
alert(err.message);
|
|
||||||
console.error(err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsUpdatingProgress(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load the topic status when the topic detail is active
|
|
||||||
useEffect(() => {
|
|
||||||
if (!topicId || !resourceId || !resourceType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsUpdatingProgress(true);
|
|
||||||
getTopicStatus({ topicId, resourceId, resourceType })
|
|
||||||
.then((status) => {
|
|
||||||
setIsUpdatingProgress(false);
|
|
||||||
setProgress(status);
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}, [topicId, resourceId, resourceType]);
|
|
||||||
|
|
||||||
// Close the topic detail when user clicks outside the topic detail
|
// Close the topic detail when user clicks outside the topic detail
|
||||||
useOutsideClick(topicRef, () => {
|
useOutsideClick(topicRef, () => {
|
||||||
setIsActive(false);
|
setIsActive(false);
|
||||||
@@ -191,10 +145,6 @@ export function TopicDetail() {
|
|||||||
resourceType === 'roadmap' ? 'roadmaps' : 'best-practices';
|
resourceType === 'roadmap' ? 'roadmaps' : 'best-practices';
|
||||||
const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`;
|
const contributionUrl = `https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/${contributionDir}/${resourceId}/content`;
|
||||||
|
|
||||||
const allowMarkingDone = ['pending', 'learning'].includes(progress);
|
|
||||||
const allowMarkingLearning = ['pending'].includes(progress);
|
|
||||||
const allowMarkingPending = ['done', 'learning'].includes(progress);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
@@ -215,71 +165,14 @@ export function TopicDetail() {
|
|||||||
<>
|
<>
|
||||||
{/* Actions for the topic */}
|
{/* Actions for the topic */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{isGuest && (
|
<TopicProgressButton
|
||||||
<div className="flex items-center gap-2">
|
topicId={topicId}
|
||||||
<button
|
resourceId={resourceId}
|
||||||
data-popup="login-popup"
|
resourceType={resourceType}
|
||||||
className="inline-flex items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
|
onClose={() => {
|
||||||
onClick={() => setIsActive(false)}
|
setIsActive(false);
|
||||||
>
|
}}
|
||||||
<img alt="Check" class="w-3" src={CheckIcon} />
|
/>
|
||||||
<span className="ml-2">Mark as Done</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
data-popup="login-popup"
|
|
||||||
className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]"
|
|
||||||
onClick={() => setIsActive(false)}
|
|
||||||
>
|
|
||||||
<img alt="Learning" class="w-4" src={ProgressIcon} />
|
|
||||||
<span className="ml-2">In Progress</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isGuest && (
|
|
||||||
<div class="flex items-center gap-2 rounded-md">
|
|
||||||
{isUpdatingProgress && (
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isUpdatingProgress && allowMarkingDone && (
|
|
||||||
<button
|
|
||||||
className="inline-flex items-center rounded-md border border-green-600 bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
|
|
||||||
onClick={() => handleUpdateResourceProgress('done')}
|
|
||||||
>
|
|
||||||
<img alt="Check" class="w-3" src={CheckIcon} />
|
|
||||||
<span className="ml-2">Mark as Done</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isUpdatingProgress && allowMarkingLearning && (
|
|
||||||
<button
|
|
||||||
className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]"
|
|
||||||
onClick={() => handleUpdateResourceProgress('learning')}
|
|
||||||
>
|
|
||||||
<img alt="Learning" className="w-4" src={ProgressIcon} />
|
|
||||||
<span className="ml-2">In Progress</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isUpdatingProgress && allowMarkingPending && (
|
|
||||||
<button
|
|
||||||
className="inline-flex items-center rounded-md border border-red-600 bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700"
|
|
||||||
onClick={() => handleUpdateResourceProgress('pending')}
|
|
||||||
>
|
|
||||||
<img alt="Check" class="h-4" src={ResetIcon} />
|
|
||||||
<span className="ml-2">Mark as Pending</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
138
src/components/TopicDetail/TopicProgressButton.tsx
Normal file
138
src/components/TopicDetail/TopicProgressButton.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { useEffect, useMemo, useState } from 'preact/hooks';
|
||||||
|
import CheckIcon from '../../icons/check.svg';
|
||||||
|
import ProgressIcon from '../../icons/progress.svg';
|
||||||
|
import ResetIcon from '../../icons/reset.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;
|
||||||
|
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TopicProgressButton(props: TopicProgressButtonProps) {
|
||||||
|
const { topicId, resourceId, resourceType, onClose } = props;
|
||||||
|
|
||||||
|
const [isUpdatingProgress, setIsUpdatingProgress] = useState(true);
|
||||||
|
const [progress, setProgress] = useState<ResourceProgressType>('pending');
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const handleUpdateResourceProgress = (progress: ResourceProgressType) => {
|
||||||
|
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 allowMarkingDone = ['pending', 'learning'].includes(progress);
|
||||||
|
const allowMarkingLearning = ['pending'].includes(progress);
|
||||||
|
const allowMarkingPending = ['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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGuest) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
data-popup="login-popup"
|
||||||
|
className="inline-flex items-center rounded-md bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<img alt="Check" class="w-3" src={CheckIcon} />
|
||||||
|
<span className="ml-2">Mark as Done</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-popup="login-popup"
|
||||||
|
className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<img alt="Learning" class="w-4" src={ProgressIcon} />
|
||||||
|
<span className="ml-2">In Progress</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex items-center gap-2 rounded-md">
|
||||||
|
{allowMarkingDone && (
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center rounded-md border border-green-600 bg-green-600 p-1 px-2 text-sm text-white hover:bg-green-700"
|
||||||
|
onClick={() => handleUpdateResourceProgress('done')}
|
||||||
|
>
|
||||||
|
<img alt="Check" class="w-3" src={CheckIcon} />
|
||||||
|
<span className="ml-1">Done</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{allowMarkingLearning && (
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center rounded-md bg-[#dad1fd] p-1 px-2 text-sm text-[#0E033B] hover:bg-[#C4B6FC]"
|
||||||
|
onClick={() => handleUpdateResourceProgress('learning')}
|
||||||
|
>
|
||||||
|
<img alt="Learning" className="w-4" src={ProgressIcon} />
|
||||||
|
<span className="ml-1">Doing</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{allowMarkingPending && (
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center rounded-md border border-red-600 bg-red-600 p-1 px-2 text-sm text-white hover:bg-red-700"
|
||||||
|
onClick={() => handleUpdateResourceProgress('pending')}
|
||||||
|
>
|
||||||
|
<img alt="Check" class="h-4" src={ResetIcon} />
|
||||||
|
<span className="ml-2">Pending</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user