|
|
@ -26,8 +26,7 @@ type ProgressStackProps = { |
|
|
|
topicDoneToday: number; |
|
|
|
topicDoneToday: number; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const MAX_PROGRESS_TO_SHOW = 5; |
|
|
|
const MAX_PROGRESS_TO_SHOW = 11; |
|
|
|
const MAX_BOOKMARKS_TO_SHOW = 5; |
|
|
|
|
|
|
|
const MAX_PROJECTS_TO_SHOW = 8; |
|
|
|
const MAX_PROJECTS_TO_SHOW = 8; |
|
|
|
|
|
|
|
|
|
|
|
type ProgressLaneProps = { |
|
|
|
type ProgressLaneProps = { |
|
|
@ -36,6 +35,7 @@ type ProgressLaneProps = { |
|
|
|
linkHref?: string; |
|
|
|
linkHref?: string; |
|
|
|
isLoading?: boolean; |
|
|
|
isLoading?: boolean; |
|
|
|
isEmpty?: boolean; |
|
|
|
isEmpty?: boolean; |
|
|
|
|
|
|
|
loadingWrapperClassName?: string; |
|
|
|
loadingSkeletonCount?: number; |
|
|
|
loadingSkeletonCount?: number; |
|
|
|
loadingSkeletonClassName?: string; |
|
|
|
loadingSkeletonClassName?: string; |
|
|
|
children: React.ReactNode; |
|
|
|
children: React.ReactNode; |
|
|
@ -43,6 +43,7 @@ type ProgressLaneProps = { |
|
|
|
emptyIcon?: LucideIcon; |
|
|
|
emptyIcon?: LucideIcon; |
|
|
|
emptyLinkText?: string; |
|
|
|
emptyLinkText?: string; |
|
|
|
emptyLinkHref?: string; |
|
|
|
emptyLinkHref?: string; |
|
|
|
|
|
|
|
className?: string; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function ProgressLane(props: ProgressLaneProps) { |
|
|
|
function ProgressLane(props: ProgressLaneProps) { |
|
|
@ -51,6 +52,7 @@ function ProgressLane(props: ProgressLaneProps) { |
|
|
|
linkText, |
|
|
|
linkText, |
|
|
|
linkHref, |
|
|
|
linkHref, |
|
|
|
isLoading = false, |
|
|
|
isLoading = false, |
|
|
|
|
|
|
|
loadingWrapperClassName = '', |
|
|
|
loadingSkeletonCount = 4, |
|
|
|
loadingSkeletonCount = 4, |
|
|
|
loadingSkeletonClassName = '', |
|
|
|
loadingSkeletonClassName = '', |
|
|
|
children, |
|
|
|
children, |
|
|
@ -59,10 +61,16 @@ function ProgressLane(props: ProgressLaneProps) { |
|
|
|
emptyMessage = `No ${title.toLowerCase()} to show`, |
|
|
|
emptyMessage = `No ${title.toLowerCase()} to show`, |
|
|
|
emptyLinkHref = '/roadmaps', |
|
|
|
emptyLinkHref = '/roadmaps', |
|
|
|
emptyLinkText = 'Explore', |
|
|
|
emptyLinkText = 'Explore', |
|
|
|
|
|
|
|
className, |
|
|
|
} = props; |
|
|
|
} = props; |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className="flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm"> |
|
|
|
<div |
|
|
|
|
|
|
|
className={cn( |
|
|
|
|
|
|
|
'flex h-full flex-col rounded-md border bg-white px-4 py-3 shadow-sm', |
|
|
|
|
|
|
|
className, |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
> |
|
|
|
{isLoading && ( |
|
|
|
{isLoading && ( |
|
|
|
<div className={'flex flex-row justify-between'}> |
|
|
|
<div className={'flex flex-row justify-between'}> |
|
|
|
<div className="h-[16px] w-[75px] animate-pulse rounded-md bg-gray-100"></div> |
|
|
|
<div className="h-[16px] w-[75px] animate-pulse rounded-md bg-gray-100"></div> |
|
|
@ -86,11 +94,13 @@ function ProgressLane(props: ProgressLaneProps) { |
|
|
|
|
|
|
|
|
|
|
|
<div className="mt-4 flex flex-grow flex-col gap-1.5"> |
|
|
|
<div className="mt-4 flex flex-grow flex-col gap-1.5"> |
|
|
|
{isLoading && ( |
|
|
|
{isLoading && ( |
|
|
|
<> |
|
|
|
<div |
|
|
|
|
|
|
|
className={cn('grid grid-cols-2 gap-2', loadingWrapperClassName)} |
|
|
|
|
|
|
|
> |
|
|
|
{Array.from({ length: loadingSkeletonCount }).map((_, index) => ( |
|
|
|
{Array.from({ length: loadingSkeletonCount }).map((_, index) => ( |
|
|
|
<CardSkeleton key={index} className={loadingSkeletonClassName} /> |
|
|
|
<CardSkeleton key={index} className={loadingSkeletonClassName} /> |
|
|
|
))} |
|
|
|
))} |
|
|
|
</> |
|
|
|
</div> |
|
|
|
)} |
|
|
|
)} |
|
|
|
{!isLoading && children} |
|
|
|
{!isLoading && children} |
|
|
|
|
|
|
|
|
|
|
@ -119,29 +129,27 @@ export function ProgressStack(props: ProgressStackProps) { |
|
|
|
const { progresses, projects, isLoading, accountStreak, topicDoneToday } = |
|
|
|
const { progresses, projects, isLoading, accountStreak, topicDoneToday } = |
|
|
|
props; |
|
|
|
props; |
|
|
|
|
|
|
|
|
|
|
|
const bookmarkedProgresses = progresses.filter( |
|
|
|
const [showAllProgresses, setShowAllProgresses] = useState(false); |
|
|
|
(progress) => progress?.isFavorite, |
|
|
|
const sortedProgresses = progresses.sort((a, b) => { |
|
|
|
); |
|
|
|
if (a.isFavorite && !b.isFavorite) { |
|
|
|
|
|
|
|
return 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const userProgresses = progresses.filter( |
|
|
|
if (!a.isFavorite && b.isFavorite) { |
|
|
|
(progress) => !progress?.isFavorite || progress?.done > 0, |
|
|
|
return -1; |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const [showAllProgresses, setShowAllProgresses] = useState(false); |
|
|
|
return 0; |
|
|
|
|
|
|
|
}); |
|
|
|
const userProgressesToShow = showAllProgresses |
|
|
|
const userProgressesToShow = showAllProgresses |
|
|
|
? userProgresses |
|
|
|
? sortedProgresses |
|
|
|
: userProgresses.slice(0, MAX_PROGRESS_TO_SHOW); |
|
|
|
: sortedProgresses.slice(0, MAX_PROGRESS_TO_SHOW); |
|
|
|
|
|
|
|
|
|
|
|
const [showAllProjects, setShowAllProjects] = useState(false); |
|
|
|
const [showAllProjects, setShowAllProjects] = useState(false); |
|
|
|
const projectsToShow = showAllProjects |
|
|
|
const projectsToShow = showAllProjects |
|
|
|
? projects |
|
|
|
? projects |
|
|
|
: projects.slice(0, MAX_PROJECTS_TO_SHOW); |
|
|
|
: projects.slice(0, MAX_PROJECTS_TO_SHOW); |
|
|
|
|
|
|
|
|
|
|
|
const [showAllBookmarks, setShowAllBookmarks] = useState(false); |
|
|
|
|
|
|
|
const bookmarksToShow = showAllBookmarks |
|
|
|
|
|
|
|
? bookmarkedProgresses |
|
|
|
|
|
|
|
: bookmarkedProgresses.slice(0, MAX_BOOKMARKS_TO_SHOW); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const totalProjectFinished = projects.filter( |
|
|
|
const totalProjectFinished = projects.filter( |
|
|
|
(project) => project.repositoryUrl, |
|
|
|
(project) => project.repositoryUrl, |
|
|
|
).length; |
|
|
|
).length; |
|
|
@ -167,73 +175,50 @@ export function ProgressStack(props: ProgressStackProps) { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div className="mt-2 grid min-h-[330px] grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3"> |
|
|
|
<div className="mt-2 grid min-h-[330px] grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3"> |
|
|
|
<div className="relative"> |
|
|
|
<div className="relative col-span-2"> |
|
|
|
{!isLoading && bookmarksToShow.length === 0 && ( |
|
|
|
{!isLoading && userProgressesToShow.length === 0 && ( |
|
|
|
<EmptyStackMessage |
|
|
|
<EmptyStackMessage |
|
|
|
number={1} |
|
|
|
number={1} |
|
|
|
title={'Bookmark Roadmaps'} |
|
|
|
title={'Bookmark some Roadmaps'} |
|
|
|
description={'Bookmark some roadmaps to access them quickly'} |
|
|
|
description={ |
|
|
|
|
|
|
|
'Bookmark some roadmaps to access them quickly and start updating your progress' |
|
|
|
|
|
|
|
} |
|
|
|
buttonText={'Explore Roadmaps'} |
|
|
|
buttonText={'Explore Roadmaps'} |
|
|
|
buttonLink={'/roadmaps'} |
|
|
|
buttonLink={'/roadmaps'} |
|
|
|
|
|
|
|
bodyClassName="max-w-[280px]" |
|
|
|
/> |
|
|
|
/> |
|
|
|
)} |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
<ProgressLane |
|
|
|
<ProgressLane |
|
|
|
title={'Bookmarks'} |
|
|
|
title="Progress & Bookmarks" |
|
|
|
isLoading={isLoading} |
|
|
|
isLoading={isLoading} |
|
|
|
loadingSkeletonCount={5} |
|
|
|
loadingSkeletonCount={MAX_PROGRESS_TO_SHOW} |
|
|
|
linkHref={'/roadmaps'} |
|
|
|
linkHref="/roadmaps" |
|
|
|
linkText={'Roadmaps'} |
|
|
|
linkText="Roadmaps" |
|
|
|
isEmpty={bookmarksToShow.length === 0} |
|
|
|
isEmpty={userProgressesToShow.length === 0} |
|
|
|
emptyIcon={Bookmark} |
|
|
|
emptyIcon={Bookmark} |
|
|
|
emptyMessage={'No bookmarks to show'} |
|
|
|
emptyMessage={'No bookmarks to show'} |
|
|
|
emptyLinkHref={'/roadmaps'} |
|
|
|
emptyLinkHref={'/roadmaps'} |
|
|
|
emptyLinkText={'Explore Roadmaps'} |
|
|
|
emptyLinkText={'Explore Roadmaps'} |
|
|
|
> |
|
|
|
> |
|
|
|
{bookmarksToShow.map((progress) => { |
|
|
|
<div className="grid grid-cols-2 gap-2"> |
|
|
|
|
|
|
|
{userProgressesToShow.length > 0 && ( |
|
|
|
|
|
|
|
<> |
|
|
|
|
|
|
|
{userProgressesToShow.map((progress) => { |
|
|
|
|
|
|
|
const isFavorite = |
|
|
|
|
|
|
|
progress.isFavorite && |
|
|
|
|
|
|
|
!progress.done && |
|
|
|
|
|
|
|
!progress.skipped; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isFavorite) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<DashboardBookmarkCard |
|
|
|
<DashboardBookmarkCard |
|
|
|
key={progress.resourceId} |
|
|
|
key={progress.resourceId} |
|
|
|
bookmark={progress} |
|
|
|
bookmark={progress} |
|
|
|
/> |
|
|
|
/> |
|
|
|
); |
|
|
|
); |
|
|
|
})} |
|
|
|
} |
|
|
|
{bookmarkedProgresses.length > MAX_BOOKMARKS_TO_SHOW && ( |
|
|
|
|
|
|
|
<ShowAllButton |
|
|
|
|
|
|
|
showAll={showAllBookmarks} |
|
|
|
|
|
|
|
setShowAll={setShowAllBookmarks} |
|
|
|
|
|
|
|
count={bookmarkedProgresses.length} |
|
|
|
|
|
|
|
maxCount={MAX_BOOKMARKS_TO_SHOW} |
|
|
|
|
|
|
|
className="mb-0.5 mt-3" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</ProgressLane> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="relative"> |
|
|
|
|
|
|
|
{!isLoading && userProgressesToShow.length === 0 && ( |
|
|
|
|
|
|
|
<EmptyStackMessage |
|
|
|
|
|
|
|
number={2} |
|
|
|
|
|
|
|
title={'Track Progress'} |
|
|
|
|
|
|
|
description={'Pick your first roadmap and start learning'} |
|
|
|
|
|
|
|
buttonText={'Explore roadmaps'} |
|
|
|
|
|
|
|
buttonLink={'/roadmaps'} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
<ProgressLane |
|
|
|
|
|
|
|
title={'Progress'} |
|
|
|
|
|
|
|
linkHref={'/roadmaps'} |
|
|
|
|
|
|
|
linkText={'Roadmaps'} |
|
|
|
|
|
|
|
isLoading={isLoading} |
|
|
|
|
|
|
|
loadingSkeletonCount={5} |
|
|
|
|
|
|
|
isEmpty={userProgressesToShow.length === 0} |
|
|
|
|
|
|
|
emptyMessage={'Update your Progress'} |
|
|
|
|
|
|
|
emptyIcon={Map} |
|
|
|
|
|
|
|
emptyLinkText={'Explore Roadmaps'} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{userProgressesToShow.length > 0 && ( |
|
|
|
|
|
|
|
<> |
|
|
|
|
|
|
|
{userProgressesToShow.map((progress) => { |
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<DashboardProgressCard |
|
|
|
<DashboardProgressCard |
|
|
|
key={progress.resourceId} |
|
|
|
key={progress.resourceId} |
|
|
@ -244,15 +229,16 @@ export function ProgressStack(props: ProgressStackProps) { |
|
|
|
</> |
|
|
|
</> |
|
|
|
)} |
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{userProgresses.length > MAX_PROGRESS_TO_SHOW && ( |
|
|
|
{sortedProgresses.length > MAX_PROGRESS_TO_SHOW && ( |
|
|
|
<ShowAllButton |
|
|
|
<ShowAllButton |
|
|
|
showAll={showAllProgresses} |
|
|
|
showAll={showAllProgresses} |
|
|
|
setShowAll={setShowAllProgresses} |
|
|
|
setShowAll={setShowAllProgresses} |
|
|
|
count={userProgresses.length} |
|
|
|
count={sortedProgresses.length} |
|
|
|
maxCount={MAX_PROGRESS_TO_SHOW} |
|
|
|
maxCount={MAX_PROGRESS_TO_SHOW} |
|
|
|
className="mb-0.5 mt-3" |
|
|
|
className="min-h-[38px] rounded-md border border-dashed leading-none" |
|
|
|
/> |
|
|
|
/> |
|
|
|
)} |
|
|
|
)} |
|
|
|
|
|
|
|
</div> |
|
|
|
</ProgressLane> |
|
|
|
</ProgressLane> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
@ -262,6 +248,7 @@ export function ProgressStack(props: ProgressStackProps) { |
|
|
|
linkHref={'/projects'} |
|
|
|
linkHref={'/projects'} |
|
|
|
linkText={'Projects'} |
|
|
|
linkText={'Projects'} |
|
|
|
isLoading={isLoading} |
|
|
|
isLoading={isLoading} |
|
|
|
|
|
|
|
loadingWrapperClassName="grid-cols-1" |
|
|
|
loadingSkeletonClassName={'h-5'} |
|
|
|
loadingSkeletonClassName={'h-5'} |
|
|
|
loadingSkeletonCount={8} |
|
|
|
loadingSkeletonCount={8} |
|
|
|
isEmpty={projectsToShow.length === 0} |
|
|
|
isEmpty={projectsToShow.length === 0} |
|
|
@ -272,7 +259,7 @@ export function ProgressStack(props: ProgressStackProps) { |
|
|
|
> |
|
|
|
> |
|
|
|
{!isLoading && projectsToShow.length === 0 && ( |
|
|
|
{!isLoading && projectsToShow.length === 0 && ( |
|
|
|
<EmptyStackMessage |
|
|
|
<EmptyStackMessage |
|
|
|
number={3} |
|
|
|
number={2} |
|
|
|
title={'Build your first project'} |
|
|
|
title={'Build your first project'} |
|
|
|
description={'Pick a project to practice and start building'} |
|
|
|
description={'Pick a project to practice and start building'} |
|
|
|
buttonText={'Explore Projects'} |
|
|
|
buttonText={'Explore Projects'} |
|
|
@ -317,7 +304,6 @@ function ShowAllButton(props: ShowAllButtonProps) { |
|
|
|
const { showAll, setShowAll, count, maxCount, className } = props; |
|
|
|
const { showAll, setShowAll, count, maxCount, className } = props; |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<span className="flex flex-grow items-end"> |
|
|
|
|
|
|
|
<button |
|
|
|
<button |
|
|
|
className={cn( |
|
|
|
className={cn( |
|
|
|
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700', |
|
|
|
'flex w-full items-center justify-center text-sm text-gray-500 hover:text-gray-700', |
|
|
@ -327,7 +313,6 @@ function ShowAllButton(props: ShowAllButtonProps) { |
|
|
|
> |
|
|
|
> |
|
|
|
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>} |
|
|
|
{!showAll ? <>+ show {count - maxCount} more</> : <>- show less</>} |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
</span> |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -341,7 +326,7 @@ function CardSkeleton(props: CardSkeletonProps) { |
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div |
|
|
|
<div |
|
|
|
className={cn( |
|
|
|
className={cn( |
|
|
|
'h-10 w-full animate-pulse rounded-md bg-gray-100', |
|
|
|
'h-[38px] w-full animate-pulse rounded-md bg-gray-100', |
|
|
|
className, |
|
|
|
className, |
|
|
|
)} |
|
|
|
)} |
|
|
|
/> |
|
|
|
/> |
|
|
|