parent
932b513d98
commit
393022c826
10 changed files with 367 additions and 462 deletions
@ -1,264 +0,0 @@ |
||||
import type { UserProgressResponse } from './FavoriteRoadmaps'; |
||||
import { CheckIcon } from '../ReactIcons/CheckIcon'; |
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite'; |
||||
import { Spinner } from '../ReactIcons/Spinner'; |
||||
import type { ResourceType } from '../../lib/resource-progress'; |
||||
import { MapIcon, Users2 } from 'lucide-react'; |
||||
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton'; |
||||
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal'; |
||||
import { type ReactNode, useState } from 'react'; |
||||
import { FeatureAnnouncement } from '../FeatureAnnouncement.tsx'; |
||||
|
||||
type ProgressRoadmapProps = { |
||||
url: string; |
||||
percentageDone: number; |
||||
allowFavorite?: boolean; |
||||
|
||||
resourceId: string; |
||||
resourceType: ResourceType; |
||||
resourceTitle: string; |
||||
isFavorite?: boolean; |
||||
}; |
||||
function HeroRoadmap(props: ProgressRoadmapProps) { |
||||
const { |
||||
url, |
||||
percentageDone, |
||||
resourceType, |
||||
resourceId, |
||||
resourceTitle, |
||||
isFavorite, |
||||
allowFavorite = true, |
||||
} = props; |
||||
|
||||
return ( |
||||
<a |
||||
href={url} |
||||
className="relative flex flex-col overflow-hidden rounded-md border border-slate-800 bg-slate-900 p-3 text-sm text-slate-400 hover:border-slate-600 hover:text-slate-300" |
||||
> |
||||
<span className="relative z-20">{resourceTitle}</span> |
||||
|
||||
<span |
||||
className="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]" |
||||
style={{ width: `${percentageDone}%` }} |
||||
></span> |
||||
|
||||
{allowFavorite && ( |
||||
<MarkFavorite |
||||
resourceId={resourceId} |
||||
resourceType={resourceType} |
||||
favorite={isFavorite} |
||||
/> |
||||
)} |
||||
</a> |
||||
); |
||||
} |
||||
|
||||
type ProgressTitleProps = { |
||||
icon: any; |
||||
isLoading?: boolean; |
||||
title: string | ReactNode; |
||||
}; |
||||
|
||||
export function HeroTitle(props: ProgressTitleProps) { |
||||
const { isLoading = false, title, icon } = props; |
||||
|
||||
return ( |
||||
<p className="mb-4 flex items-center text-sm text-gray-400"> |
||||
{!isLoading && icon} |
||||
{isLoading && ( |
||||
<span className="mr-1.5"> |
||||
<Spinner /> |
||||
</span> |
||||
)} |
||||
{title} |
||||
</p> |
||||
); |
||||
} |
||||
export type HeroTeamRoadmaps = Record<string, UserProgressResponse>; |
||||
|
||||
type ProgressListProps = { |
||||
progress: UserProgressResponse; |
||||
customRoadmaps: UserProgressResponse; |
||||
teamRoadmaps?: HeroTeamRoadmaps; |
||||
isLoading?: boolean; |
||||
}; |
||||
|
||||
export function HeroRoadmaps(props: ProgressListProps) { |
||||
const { |
||||
teamRoadmaps = {}, |
||||
progress, |
||||
isLoading = false, |
||||
customRoadmaps, |
||||
} = props; |
||||
|
||||
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false); |
||||
const [creatingRoadmapTeamId, setCreatingRoadmapTeamId] = useState<string>(); |
||||
|
||||
return ( |
||||
<div className="relative pb-12 pt-4 sm:pt-7"> |
||||
<p className="mb-7 mt-2 text-sm"> |
||||
<FeatureAnnouncement /> |
||||
</p> |
||||
{isCreatingRoadmap && ( |
||||
<CreateRoadmapModal |
||||
teamId={creatingRoadmapTeamId} |
||||
onClose={() => { |
||||
setIsCreatingRoadmap(false); |
||||
setCreatingRoadmapTeamId(undefined); |
||||
}} |
||||
/> |
||||
)} |
||||
{ |
||||
<HeroTitle |
||||
icon={ |
||||
(<CheckIcon additionalClasses="mr-1.5 h-[14px] w-[14px]" />) as any |
||||
} |
||||
isLoading={isLoading} |
||||
title="Your progress and favorite roadmaps." |
||||
/> |
||||
} |
||||
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3"> |
||||
{progress.map((resource) => ( |
||||
<HeroRoadmap |
||||
key={`${resource.resourceType}-${resource.resourceId}`} |
||||
resourceId={resource.resourceId} |
||||
resourceType={resource.resourceType} |
||||
resourceTitle={resource.resourceTitle} |
||||
isFavorite={resource.isFavorite} |
||||
percentageDone={ |
||||
((resource.skipped + resource.done) / resource.total) * 100 |
||||
} |
||||
url={ |
||||
resource.resourceType === 'roadmap' |
||||
? `/${resource.resourceId}` |
||||
: `/best-practices/${resource.resourceId}` |
||||
} |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className="mt-5"> |
||||
{ |
||||
<HeroTitle |
||||
icon={<MapIcon className="mr-1.5 h-[14px] w-[14px]" />} |
||||
title="Your custom roadmaps" |
||||
/> |
||||
} |
||||
|
||||
{customRoadmaps.length === 0 && ( |
||||
<p className="rounded-md border border-dashed border-gray-800 p-2 text-sm text-gray-600"> |
||||
You haven't created any custom roadmaps yet.{' '} |
||||
<button |
||||
className="text-gray-500 underline underline-offset-2 hover:text-gray-400" |
||||
onClick={() => setIsCreatingRoadmap(true)} |
||||
> |
||||
Create one! |
||||
</button> |
||||
</p> |
||||
)} |
||||
|
||||
{customRoadmaps.length > 0 && ( |
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3"> |
||||
{customRoadmaps.map((customRoadmap) => { |
||||
return ( |
||||
<HeroRoadmap |
||||
key={customRoadmap.resourceId} |
||||
resourceId={customRoadmap.resourceId} |
||||
resourceType={'roadmap'} |
||||
resourceTitle={customRoadmap.resourceTitle} |
||||
percentageDone={ |
||||
((customRoadmap.skipped + customRoadmap.done) / |
||||
customRoadmap.total) * |
||||
100 |
||||
} |
||||
url={`/r/${customRoadmap?.roadmapSlug}`} |
||||
allowFavorite={false} |
||||
/> |
||||
); |
||||
})} |
||||
|
||||
<CreateRoadmapButton /> |
||||
</div> |
||||
)} |
||||
</div> |
||||
|
||||
{Object.keys(teamRoadmaps).map((teamName) => { |
||||
const currentTeam: UserProgressResponse[0]['team'] = |
||||
teamRoadmaps?.[teamName]?.[0]?.team; |
||||
const roadmapsList = teamRoadmaps[teamName].filter( |
||||
(roadmap) => !!roadmap.resourceTitle, |
||||
); |
||||
const canManageTeam = ['admin', 'manager'].includes(currentTeam?.role!); |
||||
|
||||
return ( |
||||
<div className="mt-5" key={teamName}> |
||||
{ |
||||
<HeroTitle |
||||
icon={<Users2 className="mr-1.5 h-[14px] w-[14px]" />} |
||||
title={ |
||||
<> |
||||
Team{' '} |
||||
<a |
||||
className="mx-1 font-medium underline underline-offset-2 transition-colors hover:text-gray-300" |
||||
href={`/team/activity?t=${currentTeam?.id}`} |
||||
> |
||||
{teamName} |
||||
</a> |
||||
Roadmaps |
||||
</> |
||||
} |
||||
/> |
||||
} |
||||
|
||||
{roadmapsList.length === 0 && ( |
||||
<p className="rounded-md border border-dashed border-gray-800 p-2 text-sm text-gray-600"> |
||||
Team does not have any roadmaps yet.{' '} |
||||
{canManageTeam && ( |
||||
<button |
||||
className="text-gray-500 underline underline-offset-2 hover:text-gray-400" |
||||
onClick={() => { |
||||
setCreatingRoadmapTeamId(currentTeam?.id); |
||||
setIsCreatingRoadmap(true); |
||||
}} |
||||
> |
||||
Create one! |
||||
</button> |
||||
)} |
||||
</p> |
||||
)} |
||||
|
||||
{roadmapsList.length > 0 && ( |
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3"> |
||||
{roadmapsList.map((customRoadmap) => { |
||||
return ( |
||||
<HeroRoadmap |
||||
key={customRoadmap.resourceId} |
||||
resourceId={customRoadmap.resourceId} |
||||
resourceType={'roadmap'} |
||||
resourceTitle={customRoadmap.resourceTitle} |
||||
percentageDone={ |
||||
((customRoadmap.skipped + customRoadmap.done) / |
||||
customRoadmap.total) * |
||||
100 |
||||
} |
||||
url={`/r/${customRoadmap?.roadmapSlug}`} |
||||
allowFavorite={false} |
||||
/> |
||||
); |
||||
})} |
||||
|
||||
{canManageTeam && ( |
||||
<CreateRoadmapButton |
||||
teamId={currentTeam?.id} |
||||
text="Create Team Roadmap" |
||||
/> |
||||
)} |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue