mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-25 08:35:42 +02:00
Move to separate files
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
"enabled": false
|
||||
},
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1738019390029
|
||||
"lastUpdateCheck": 1739229597159
|
||||
}
|
||||
}
|
@@ -17,11 +17,11 @@ import { $accountStreak, type StreakResponse } from '../../stores/streak';
|
||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||
import {
|
||||
FavoriteRoadmaps,
|
||||
HeroRoadmap,
|
||||
type AIRoadmapType,
|
||||
} from '../HeroSection/FavoriteRoadmaps.tsx';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
|
||||
import { HeroRoadmap } from '../HeroSection/HeroRoadmap.tsx';
|
||||
|
||||
type UserDashboardResponse = {
|
||||
name: string;
|
||||
@@ -106,7 +106,7 @@ function DashboardStats(props: DashboardStatsProps) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="container flex items-center justify-between gap-2 pb-2 pt-6 text-sm text-slate-400 mb-3">
|
||||
<div className="container mb-3 flex items-center justify-between gap-2 pb-2 pt-6 text-sm text-slate-400">
|
||||
<div className="flex items-center gap-2">
|
||||
<DashboardStatItem
|
||||
icon={Zap}
|
||||
|
@@ -3,25 +3,18 @@ import {
|
||||
MapIcon,
|
||||
Plus,
|
||||
Sparkle,
|
||||
ThumbsUp,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Eye,
|
||||
EyeOff,
|
||||
CircleDashed,
|
||||
Circle,
|
||||
} from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ResourceType } from '../../lib/resource-progress.ts';
|
||||
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite.tsx';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage.tsx';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
import { useState } from 'react';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
|
||||
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage.tsx';
|
||||
import { HeroProject } from './HeroProject';
|
||||
import { HeroRoadmap } from './HeroRoadmap';
|
||||
import { HeroTitle } from './HeroTitle';
|
||||
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx';
|
||||
|
||||
export type AIRoadmapType = {
|
||||
id: string;
|
||||
@@ -29,103 +22,6 @@ export type AIRoadmapType = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
type ProgressRoadmapProps = {
|
||||
url: string;
|
||||
percentageDone: number;
|
||||
allowFavorite?: boolean;
|
||||
|
||||
resourceId: string;
|
||||
resourceType: ResourceType;
|
||||
resourceTitle: string;
|
||||
isFavorite?: boolean;
|
||||
|
||||
isTrackable?: boolean;
|
||||
isNew?: boolean;
|
||||
};
|
||||
|
||||
export function HeroRoadmap(props: ProgressRoadmapProps) {
|
||||
const {
|
||||
url,
|
||||
percentageDone,
|
||||
resourceType,
|
||||
resourceId,
|
||||
resourceTitle,
|
||||
isFavorite,
|
||||
allowFavorite = true,
|
||||
isTrackable = true,
|
||||
isNew = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
className={cn(
|
||||
'relative flex flex-col overflow-hidden rounded-md border p-3 text-sm text-slate-400 hover:text-slate-300',
|
||||
{
|
||||
'border-slate-800 bg-slate-900 hover:border-slate-600': isTrackable,
|
||||
'border-slate-700/50 bg-slate-800/50 hover:border-slate-600/70':
|
||||
!isTrackable,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span title={resourceTitle} className="relative z-20 truncate">
|
||||
{resourceTitle}
|
||||
</span>
|
||||
|
||||
{isTrackable && (
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isNew && (
|
||||
<span className="absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-purple-300">
|
||||
<span className="mr-1.5 flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-2 w-2 animate-ping rounded-full bg-purple-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-purple-500" />
|
||||
</span>
|
||||
New
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
type HeroTitleProps = {
|
||||
icon: any;
|
||||
isLoading?: boolean;
|
||||
title: string | ReactNode;
|
||||
rightContent?: ReactNode;
|
||||
};
|
||||
|
||||
function HeroTitle(props: HeroTitleProps) {
|
||||
const { isLoading = false, title, icon, rightContent } = props;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="flex items-center text-sm text-gray-400">
|
||||
{!isLoading && icon}
|
||||
{isLoading && (
|
||||
<span className="mr-1.5">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
{title}
|
||||
</p>
|
||||
<div>{rightContent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type FavoriteRoadmapsProps = {
|
||||
progress: UserProgress[];
|
||||
projects: (ProjectStatusDocument & {
|
||||
@@ -137,56 +33,15 @@ type FavoriteRoadmapsProps = {
|
||||
isAllCollapsed: boolean;
|
||||
};
|
||||
|
||||
type HeroProjectProps = {
|
||||
project: ProjectStatusDocument & {
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function HeroProject({ project }: HeroProjectProps) {
|
||||
return (
|
||||
<a
|
||||
href={`/projects/${project.projectId}`}
|
||||
className="group relative flex flex-col justify-between gap-2 rounded-md border border-slate-800 bg-slate-900 p-3.5 hover:border-slate-600"
|
||||
>
|
||||
<div className="relative z-10 flex items-start justify-between gap-2">
|
||||
<h3 className="truncate font-medium text-slate-300 group-hover:text-slate-100">
|
||||
{project.title}
|
||||
</h3>
|
||||
<span
|
||||
className={cn(
|
||||
'absolute -right-2 -top-2 flex flex-shrink-0 items-center gap-1 rounded-full text-xs uppercase tracking-wide',
|
||||
{
|
||||
'text-green-600/50': project.submittedAt && project.repositoryUrl,
|
||||
'text-yellow-600': !project.submittedAt || !project.repositoryUrl,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{project.submittedAt && project.repositoryUrl ? 'Done' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative z-10 flex items-center gap-2 text-xs text-slate-400">
|
||||
{project.submittedAt && project.repositoryUrl && (
|
||||
<span className="flex items-center gap-1">
|
||||
<ThumbsUp className="h-3 w-3" />
|
||||
{project.upvotes}
|
||||
</span>
|
||||
)}
|
||||
{project.startedAt && (
|
||||
<span>Started {getRelativeTimeString(project.startedAt)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-slate-800/50 via-transparent to-transparent" />
|
||||
{project.submittedAt && project.repositoryUrl && (
|
||||
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-green-950/20 via-transparent to-transparent" />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
const { progress, isLoading, customRoadmaps, aiRoadmaps, projects, isAllCollapsed } = props;
|
||||
const {
|
||||
progress,
|
||||
isLoading,
|
||||
customRoadmaps,
|
||||
aiRoadmaps,
|
||||
projects,
|
||||
isAllCollapsed,
|
||||
} = props;
|
||||
const [showCompleted, setShowCompleted] = useState(false);
|
||||
|
||||
const completedProjects = projects.filter(
|
||||
@@ -203,10 +58,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className={cn("", {
|
||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
||||
"py-2": isAllCollapsed
|
||||
})}>
|
||||
<div
|
||||
className={cn('', {
|
||||
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||
'py-2': isAllCollapsed,
|
||||
})}
|
||||
>
|
||||
<div className="container">
|
||||
<HeroTitle
|
||||
icon={
|
||||
@@ -218,7 +75,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
title="Your progress and bookmarks"
|
||||
/>
|
||||
{!isLoading && progress.length > 0 && !isAllCollapsed && (
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
||||
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{progress.map((resource) => (
|
||||
<HeroRoadmap
|
||||
key={`${resource.resourceType}-${resource.resourceId}`}
|
||||
@@ -242,10 +99,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("", {
|
||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
||||
"py-2": isAllCollapsed
|
||||
})}>
|
||||
<div
|
||||
className={cn('', {
|
||||
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||
'py-2': isAllCollapsed,
|
||||
})}
|
||||
>
|
||||
<div className="container">
|
||||
<HeroTitle
|
||||
icon={(<MapIcon className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
||||
@@ -253,7 +112,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
title="Your custom roadmaps"
|
||||
/>
|
||||
{!isLoading && customRoadmaps.length > 0 && !isAllCollapsed && (
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
||||
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{customRoadmaps.map((customRoadmap) => (
|
||||
<HeroRoadmap
|
||||
key={customRoadmap.resourceId}
|
||||
@@ -275,10 +134,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("", {
|
||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
||||
"py-2": isAllCollapsed
|
||||
})}>
|
||||
<div
|
||||
className={cn('', {
|
||||
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||
'py-2': isAllCollapsed,
|
||||
})}
|
||||
>
|
||||
<div className="container">
|
||||
<HeroTitle
|
||||
icon={(<Sparkle className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
||||
@@ -286,7 +147,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
title="Your AI roadmaps"
|
||||
/>
|
||||
{!isLoading && aiRoadmaps.length > 0 && !isAllCollapsed && (
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
||||
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{aiRoadmaps.map((aiRoadmap) => (
|
||||
<HeroRoadmap
|
||||
key={aiRoadmap.id}
|
||||
@@ -314,10 +175,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("", {
|
||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
||||
"py-2": isAllCollapsed
|
||||
})}>
|
||||
<div
|
||||
className={cn('', {
|
||||
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||
'py-2': isAllCollapsed,
|
||||
})}
|
||||
>
|
||||
<div className="container">
|
||||
<HeroTitle
|
||||
icon={
|
||||
@@ -342,7 +205,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||
}
|
||||
/>
|
||||
{!isLoading && projectsToShow.length > 0 && !isAllCollapsed && (
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
||||
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||
{projectsToShow.map((project) => (
|
||||
<HeroProject key={project._id} project={project} />
|
||||
))}
|
||||
|
52
src/components/HeroSection/HeroProject.tsx
Normal file
52
src/components/HeroSection/HeroProject.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ThumbsUp } from 'lucide-react';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
|
||||
|
||||
type HeroProjectProps = {
|
||||
project: ProjectStatusDocument & {
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function HeroProject({ project }: HeroProjectProps) {
|
||||
return (
|
||||
<a
|
||||
href={`/projects/${project.projectId}`}
|
||||
className="group relative flex flex-col justify-between gap-2 rounded-md border border-slate-800 bg-slate-900 p-3.5 hover:border-slate-600"
|
||||
>
|
||||
<div className="relative z-10 flex items-start justify-between gap-2">
|
||||
<h3 className="truncate font-medium text-slate-300 group-hover:text-slate-100">
|
||||
{project.title}
|
||||
</h3>
|
||||
<span
|
||||
className={cn(
|
||||
'absolute -right-2 -top-2 flex flex-shrink-0 items-center gap-1 rounded-full text-xs uppercase tracking-wide',
|
||||
{
|
||||
'text-green-600/50': project.submittedAt && project.repositoryUrl,
|
||||
'text-yellow-600': !project.submittedAt || !project.repositoryUrl,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{project.submittedAt && project.repositoryUrl ? 'Done' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative z-10 flex items-center gap-2 text-xs text-slate-400">
|
||||
{project.submittedAt && project.repositoryUrl && (
|
||||
<span className="flex items-center gap-1">
|
||||
<ThumbsUp className="h-3 w-3" />
|
||||
{project.upvotes}
|
||||
</span>
|
||||
)}
|
||||
{project.startedAt && (
|
||||
<span>Started {getRelativeTimeString(project.startedAt)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-slate-800/50 via-transparent to-transparent" />
|
||||
{project.submittedAt && project.repositoryUrl && (
|
||||
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-green-950/20 via-transparent to-transparent" />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
74
src/components/HeroSection/HeroRoadmap.tsx
Normal file
74
src/components/HeroSection/HeroRoadmap.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import type { ResourceType } from '../../lib/resource-progress.ts';
|
||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite.tsx';
|
||||
|
||||
type ProgressRoadmapProps = {
|
||||
url: string;
|
||||
percentageDone: number;
|
||||
allowFavorite?: boolean;
|
||||
|
||||
resourceId: string;
|
||||
resourceType: ResourceType;
|
||||
resourceTitle: string;
|
||||
isFavorite?: boolean;
|
||||
|
||||
isTrackable?: boolean;
|
||||
isNew?: boolean;
|
||||
};
|
||||
|
||||
export function HeroRoadmap(props: ProgressRoadmapProps) {
|
||||
const {
|
||||
url,
|
||||
percentageDone,
|
||||
resourceType,
|
||||
resourceId,
|
||||
resourceTitle,
|
||||
isFavorite,
|
||||
allowFavorite = true,
|
||||
isTrackable = true,
|
||||
isNew = false,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={url}
|
||||
className={cn(
|
||||
'relative flex flex-col overflow-hidden rounded-md border p-3 text-sm text-slate-400 hover:text-slate-300',
|
||||
{
|
||||
'border-slate-800 bg-slate-900 hover:border-slate-600': isTrackable,
|
||||
'border-slate-700/50 bg-slate-800/50 hover:border-slate-600/70':
|
||||
!isTrackable,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span title={resourceTitle} className="relative z-20 truncate">
|
||||
{resourceTitle}
|
||||
</span>
|
||||
|
||||
{isTrackable && (
|
||||
<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}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isNew && (
|
||||
<span className="absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-purple-300">
|
||||
<span className="mr-1.5 flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-2 w-2 animate-ping rounded-full bg-purple-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-purple-500" />
|
||||
</span>
|
||||
New
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
28
src/components/HeroSection/HeroTitle.tsx
Normal file
28
src/components/HeroSection/HeroTitle.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
||||
|
||||
type HeroTitleProps = {
|
||||
icon: any;
|
||||
isLoading?: boolean;
|
||||
title: string | ReactNode;
|
||||
rightContent?: ReactNode;
|
||||
};
|
||||
|
||||
export function HeroTitle(props: HeroTitleProps) {
|
||||
const { isLoading = false, title, icon, rightContent } = props;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="flex items-center text-sm text-gray-400">
|
||||
{!isLoading && icon}
|
||||
{isLoading && (
|
||||
<span className="mr-1.5">
|
||||
<Spinner />
|
||||
</span>
|
||||
)}
|
||||
{title}
|
||||
</p>
|
||||
<div>{rightContent}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user