1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-01 21:32:35 +02:00
This commit is contained in:
Arik Chakma
2025-07-05 01:42:58 +06:00
parent 0bb701ee13
commit 7a07d02402
4 changed files with 105 additions and 29 deletions

View File

@@ -1,12 +1,17 @@
import { PersonStandingIcon } from 'lucide-react'; import { Loader2Icon, PersonStandingIcon } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { usePersonalizedRoadmap } from '../../hooks/use-personalized-roadmap'; import { usePersonalizedRoadmap } from '../../hooks/use-personalized-roadmap';
import { renderTopicProgress } from '../../lib/resource-progress'; import {
loadFreshProgress,
renderTopicProgress,
} from '../../lib/resource-progress';
import { PersonalizedRoadmapModal } from './PersonalizedRoadmapModal'; import { PersonalizedRoadmapModal } from './PersonalizedRoadmapModal';
import { useMutation } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { httpPost } from '../../lib/query-http'; import { httpPost } from '../../lib/query-http';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { queryClient } from '../../stores/query-client'; import { queryClient } from '../../stores/query-client';
import { userResourceProgressOptions } from '../../queries/resource-progress';
import { useAuth } from '../../hooks/use-auth';
type BulkUpdateResourceProgressBody = { type BulkUpdateResourceProgressBody = {
done: string[]; done: string[];
@@ -23,27 +28,38 @@ export function PersonalizedRoadmap(props: PersonalizedRoadmapProps) {
const { roadmapId } = props; const { roadmapId } = props;
const toast = useToast(); const toast = useToast();
const currentUser = useAuth();
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const { const { data: userResourceProgress } = useQuery(
mutate: bulkUpdateResourceProgress, userResourceProgressOptions('roadmap', roadmapId),
isPending: isBulkUpdating,
isSuccess: isBulkUpdateSuccess,
} = useMutation(
{
mutationFn: (body: BulkUpdateResourceProgressBody) => {
return httpPost(`/v1-bulk-update-resource-progress/${roadmapId}`, body);
},
onError: (error) => {
toast.error(
error?.message ?? 'Something went wrong, please try again.',
);
},
},
queryClient, queryClient,
); );
const { generatePersonalizedRoadmap } = usePersonalizedRoadmap({ const { mutate: bulkUpdateResourceProgress, isPending: isBulkUpdating } =
useMutation(
{
mutationFn: (body: BulkUpdateResourceProgressBody) => {
return httpPost(
`/v1-bulk-update-resource-progress/${roadmapId}`,
body,
);
},
onError: (error) => {
toast.error(
error?.message ?? 'Something went wrong, please try again.',
);
},
onSuccess: () => {
queryClient.invalidateQueries(
userResourceProgressOptions('roadmap', roadmapId),
);
},
},
queryClient,
);
const { generatePersonalizedRoadmap, status } = usePersonalizedRoadmap({
roadmapId, roadmapId,
onStart: () => { onStart: () => {
setIsModalOpen(false); setIsModalOpen(false);
@@ -55,6 +71,10 @@ export function PersonalizedRoadmap(props: PersonalizedRoadmapProps) {
}); });
}, },
onFinish: (data) => { onFinish: (data) => {
console.log('-'.repeat(20));
console.log('onFinish', data);
console.log('-'.repeat(20));
bulkUpdateResourceProgress({ bulkUpdateResourceProgress({
skipped: data.topicIds, skipped: data.topicIds,
learning: [], learning: [],
@@ -64,21 +84,65 @@ export function PersonalizedRoadmap(props: PersonalizedRoadmapProps) {
}, },
}); });
const { mutate: clearResourceProgress, isPending: isClearing } = useMutation(
{
mutationFn: () => {
return httpPost(`/v1-clear-resource-progress`, {
resourceId: roadmapId,
resourceType: 'roadmap',
});
},
onError: (error) => {
toast.error(
error?.message ?? 'Something went wrong, please try again.',
);
},
onSuccess: () => {
toast.success('Progress cleared successfully.');
localStorage.removeItem(
`roadmap-${roadmapId}-${currentUser?.id}-progress`,
);
localStorage.removeItem(
`roadmap-${roadmapId}-${currentUser?.id}-favorite`,
);
window.location.reload();
},
},
queryClient,
);
const isGenerating = status !== 'idle' || isBulkUpdating || isClearing;
return ( return (
<> <>
{isModalOpen && ( {isModalOpen && (
<PersonalizedRoadmapModal <PersonalizedRoadmapModal
roadmapId={roadmapId}
onClose={() => setIsModalOpen(false)} onClose={() => setIsModalOpen(false)}
onSubmit={generatePersonalizedRoadmap} onSubmit={(information) => {
const { skipped = [] } = userResourceProgress ?? {};
for (const topicId of skipped) {
renderTopicProgress(topicId, 'pending');
}
generatePersonalizedRoadmap(information);
}}
onClearProgress={() => {
setIsModalOpen(false);
clearResourceProgress();
}}
/> />
)} )}
<button <button
className="group inline-flex items-center gap-1.5 border-b-2 border-b-transparent px-2 pb-2.5 text-sm font-normal text-gray-400 transition-colors hover:text-gray-700" className="group inline-flex items-center gap-1.5 border-b-2 border-b-transparent px-2 pb-2.5 text-sm font-normal text-gray-400 transition-colors hover:text-gray-700"
onClick={() => setIsModalOpen(true)} onClick={() => setIsModalOpen(true)}
disabled={isGenerating}
> >
<PersonStandingIcon className="h-4 w-4 shrink-0" /> {isGenerating ? (
<Loader2Icon className="h-4 w-4 shrink-0 animate-spin" />
) : (
<PersonStandingIcon className="h-4 w-4 shrink-0" />
)}
<span>Personalized</span> <span>Personalized</span>
</button> </button>
</> </>

View File

@@ -1,13 +1,14 @@
import { PersonStandingIcon } from 'lucide-react'; import { PersonStandingIcon, XIcon } from 'lucide-react';
import { useId, useState, type FormEvent } from 'react'; import { useId, useState, type FormEvent } from 'react';
type PersonalizedRoadmapFormProps = { type PersonalizedRoadmapFormProps = {
info?: string; info?: string;
onSubmit: (info: string) => void; onSubmit: (info: string) => void;
onClearProgress: () => void;
}; };
export function PersonalizedRoadmapForm(props: PersonalizedRoadmapFormProps) { export function PersonalizedRoadmapForm(props: PersonalizedRoadmapFormProps) {
const { info: defaultInfo, onSubmit } = props; const { info: defaultInfo, onSubmit, onClearProgress } = props;
const [info, setInfo] = useState(defaultInfo || ''); const [info, setInfo] = useState(defaultInfo || '');
const infoFieldId = useId(); const infoFieldId = useId();
@@ -34,7 +35,15 @@ export function PersonalizedRoadmapForm(props: PersonalizedRoadmapFormProps) {
/> />
</div> </div>
<div className="mt-2 flex items-center justify-end"> <div className="mt-2 grid grid-cols-2 gap-2">
<button
type="button"
className="flex items-center gap-2 rounded-xl border border-gray-200 p-2 px-4 text-gray-600 hover:bg-gray-100 focus:outline-none"
onClick={onClearProgress}
>
<XIcon className="h-4 w-4" />
Clear Progress
</button>
<button <button
type="submit" type="submit"
className="flex items-center gap-2 rounded-xl bg-black p-2 px-4 text-white hover:opacity-90 focus:outline-none" className="flex items-center gap-2 rounded-xl bg-black p-2 px-4 text-white hover:opacity-90 focus:outline-none"

View File

@@ -4,17 +4,20 @@ import { Modal } from '../Modal';
import { PersonalizedRoadmapForm } from './PersonalizedRoadmapForm'; import { PersonalizedRoadmapForm } from './PersonalizedRoadmapForm';
type PersonalizedRoadmapModalProps = { type PersonalizedRoadmapModalProps = {
roadmapId: string;
onClose: () => void; onClose: () => void;
onSubmit: (information: string) => void; onSubmit: (information: string) => void;
onClearProgress: () => void;
}; };
export function PersonalizedRoadmapModal(props: PersonalizedRoadmapModalProps) { export function PersonalizedRoadmapModal(props: PersonalizedRoadmapModalProps) {
const { roadmapId, onClose, onSubmit } = props; const { onClose, onSubmit, onClearProgress } = props;
return ( return (
<Modal onClose={onClose} bodyClassName="rounded-2xl"> <Modal onClose={onClose} bodyClassName="rounded-2xl">
<PersonalizedRoadmapForm onSubmit={onSubmit} /> <PersonalizedRoadmapForm
onSubmit={onSubmit}
onClearProgress={onClearProgress}
/>
</Modal> </Modal>
); );
} }

View File

@@ -206,7 +206,7 @@ export async function getResourceProgress(
return progress; return progress;
} }
async function loadFreshProgress( export async function loadFreshProgress(
resourceType: ResourceType, resourceType: ResourceType,
resourceId: string, resourceId: string,
) { ) {