-
- setSelectedTeamId(undefined)}
- avatar={userAvatar}
- />
- {isLoading && (
- <>
-
-
-
- >
- )}
-
- {!isLoading && (
- <>
- {teamList.map((team) => {
- const { avatar } = team;
- const avatarUrl = avatar
- ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
- : '/images/default-avatar.png';
- return (
- {
- setSelectedTeamId(team._id);
- },
- })}
- avatar={avatarUrl}
- />
- );
- })}
-
- >
- )}
-
-
- {!selectedTeamId && (
+
+
- )}
- {selectedTeamId &&
}
+
);
}
-
-function DashboardTabLoading() {
- return (
-
- );
-}
diff --git a/src/components/Dashboard/DashboardProgressCard.tsx b/src/components/Dashboard/DashboardProgressCard.tsx
new file mode 100644
index 000000000..b2041c523
--- /dev/null
+++ b/src/components/Dashboard/DashboardProgressCard.tsx
@@ -0,0 +1,49 @@
+import { getPercentage } from '../../helper/number';
+import type { UserProgress } from '../TeamProgress/TeamProgressPage';
+
+type DashboardProgressCardProps = {
+ progress: UserProgress;
+};
+
+export function DashboardProgressCard(props: DashboardProgressCardProps) {
+ const { progress } = props;
+
+ const {
+ resourceType,
+ resourceId,
+ resourceTitle,
+ total: totalCount,
+ done: doneCount,
+ skipped: skippedCount,
+ roadmapSlug,
+ isCustomResource,
+ } = progress;
+
+ let url =
+ resourceType === 'roadmap'
+ ? `/${resourceId}`
+ : `/best-practices/${resourceId}`;
+
+ if (isCustomResource) {
+ url = `/r/${roadmapSlug}`;
+ }
+
+ const totalMarked = doneCount + skippedCount;
+ const progressPercentage = getPercentage(totalMarked, totalCount);
+
+ return (
+
+ {resourceTitle}
+
+
+
+ );
+}
diff --git a/src/components/Dashboard/ListDashboardCustomProgress.tsx b/src/components/Dashboard/ListDashboardCustomProgress.tsx
new file mode 100644
index 000000000..d2638c81f
--- /dev/null
+++ b/src/components/Dashboard/ListDashboardCustomProgress.tsx
@@ -0,0 +1,78 @@
+import type { UserProgress } from '../TeamProgress/TeamProgressPage';
+import { DashboardProgressCard } from './DashboardProgressCard';
+import { DashboardProgressCardSkeleton } from './ListDashboardProgress';
+import { DashboardCardLink } from './DashboardCardLink';
+import { useState } from 'react';
+import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
+
+type ListDashboardCustomProgressProps = {
+ progresses: UserProgress[];
+ isLoading?: boolean;
+ isCustomResources?: boolean;
+};
+
+export function ListDashboardCustomProgress(
+ props: ListDashboardCustomProgressProps,
+) {
+ const { progresses, isLoading = false } = props;
+ const [isCreateCustomRoadmapModalOpen, setIsCreateCustomRoadmapModalOpen] =
+ useState(false);
+
+ if (!isLoading && progresses.length === 0) {
+ return (
+
+ );
+ }
+
+ const customRoadmapModal = isCreateCustomRoadmapModalOpen ? (
+
setIsCreateCustomRoadmapModalOpen(false)}
+ onCreated={(roadmap) => {
+ window.location.href = `${
+ import.meta.env.PUBLIC_EDITOR_APP_URL
+ }/${roadmap?._id}`;
+ return;
+ }}
+ />
+ ) : null;
+
+ return (
+ <>
+ {customRoadmapModal}
+
+ Custom Roadmaps
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 8 }).map((_, index) => (
+
+ ))}
+
+ ) : (
+
+ {progresses.map((progress) => (
+
+ ))}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/Dashboard/ListDashboardProgress.tsx b/src/components/Dashboard/ListDashboardProgress.tsx
new file mode 100644
index 000000000..258d7c665
--- /dev/null
+++ b/src/components/Dashboard/ListDashboardProgress.tsx
@@ -0,0 +1,53 @@
+import { getPercentage } from '../../helper/number';
+import { getUser } from '../../lib/jwt';
+import type { UserProgress } from '../TeamProgress/TeamProgressPage';
+import { DashboardProgressCard } from './DashboardProgressCard';
+
+type ListDashboardProgressProps = {
+ progresses: UserProgress[];
+ isLoading?: boolean;
+ isCustomResources?: boolean;
+};
+
+export function ListDashboardProgress(props: ListDashboardProgressProps) {
+ const { progresses, isLoading = false } = props;
+
+ if (!isLoading && progresses.length === 0) {
+ return null;
+ }
+
+ return (
+ <>
+
+ Progress and Bookmarks
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 8 }).map((_, index) => (
+
+ ))}
+
+ ) : (
+
+ {progresses.map((progress) => (
+
+ ))}
+
+ )}
+ >
+ );
+}
+
+type DashboardProgressCardSkeletonProps = {};
+
+export function DashboardProgressCardSkeleton(
+ props: DashboardProgressCardSkeletonProps,
+) {
+ return (
+
+ );
+}
diff --git a/src/components/Dashboard/PersonalDashboard.tsx b/src/components/Dashboard/PersonalDashboard.tsx
index 038d12aa1..073e4510c 100644
--- a/src/components/Dashboard/PersonalDashboard.tsx
+++ b/src/components/Dashboard/PersonalDashboard.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useState, type ReactNode } from 'react';
import { httpGet } from '../../lib/http';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
@@ -10,6 +10,11 @@ import { LoadingProgress } from './LoadingProgress';
import { ArrowUpRight, Pencil, Plus } from 'lucide-react';
import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
+import { getCurrentPeriod } from '../../lib/date';
+import { ListDashboardProgress } from './ListDashboardProgress';
+import { ListDashboardCustomProgress } from './ListDashboardCustomProgress';
+import { DashboardCardLink } from './DashboardCardLink';
+import { RecommendedRoadmaps } from './RecommendedRoadmaps';
type UserDashboardResponse = {
name: string;
@@ -27,6 +32,7 @@ export type BuiltInRoadmap = {
title: string;
description: string;
isFavorite?: boolean;
+ relatedRoadmapIds?: string[];
};
type PersonalDashboardProps = {
@@ -44,8 +50,6 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
- const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
- const [projectDetails, setProjectDetails] = useState([]);
const [personalDashboardDetails, setPersonalDashboardDetails] =
useState();
@@ -73,26 +77,8 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
setPersonalDashboardDetails(progressList);
}
- async function loadAllProjectDetails() {
- const { error, response } = await httpGet(`/pages.json`);
-
- if (error) {
- toast.error(error.message || 'Something went wrong');
- return;
- }
-
- if (!response) {
- return [];
- }
-
- const allProjects = response.filter((page) => page.group === 'Projects');
- setProjectDetails(allProjects);
- }
-
useEffect(() => {
- Promise.allSettled([loadProgress(), loadAllProjectDetails()]).finally(() =>
- setIsLoading(false),
- );
+ loadProgress().finally(() => setIsLoading(false));
}, []);
useEffect(() => {
@@ -125,294 +111,136 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
return updatedAtB.getTime() - updatedAtA.getTime();
});
- const enrichedProjects =
- personalDashboardDetails?.projects
- ?.map((project) => {
- const projectDetail = projectDetails.find(
- (page) => page.id === project.projectId,
- );
-
- return {
- ...project,
- title: projectDetail?.title || 'N/A',
- };
- })
- ?.sort((a, b) => {
- const isPendingA = !a.repositoryUrl && !a.submittedAt;
- const isPendingB = !b.repositoryUrl && !b.submittedAt;
-
- if (isPendingA && !isPendingB) {
- return -1;
- }
-
- if (!isPendingA && isPendingB) {
- return 1;
- }
-
- return 0;
- }) || [];
-
const { avatar, name, headline, email, username } =
personalDashboardDetails || {};
const avatarLink = avatar
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}`
: '/images/default-avatar.png';
+ const currentPeriod = getCurrentPeriod();
+
+ const relatedRoadmapIds = [...builtInRoleRoadmaps, ...builtInSkillRoadmaps]
+ .filter((roadmap) =>
+ learningRoadmapsToShow?.some(
+ (learningRoadmap) => learningRoadmap.resourceId === roadmap.id,
+ ),
+ )
+ .flatMap((roadmap) => roadmap.relatedRoadmapIds)
+ .filter(Boolean);
+
+ const recommendedRoadmapIds = new Set(
+ relatedRoadmapIds.length === 0
+ ? ['frontend', 'backend', 'devops', 'ai-data-scientist', 'full-stack']
+ : relatedRoadmapIds,
+ );
+
+ const recommendedRoadmaps = [
+ ...builtInRoleRoadmaps,
+ ...builtInSkillRoadmaps,
+ ].filter((roadmap) => recommendedRoadmapIds.has(roadmap.id));
+
return (
-
- {isCreatingRoadmap && (
- {
- setIsCreatingRoadmap(false);
- }}
- />
+
+ {isLoading ? (
+
+ ) : (
+
+ Hi {name}, good {currentPeriod}!
+
)}
- {isLoading && (
-
- )}
- {!isLoading && (
-
-
-
-
- Best Practices
-
-
+
+
+
+
+
+
);
}
-type ListRoadmapsProps = {
- roadmaps: BuiltInRoadmap[];
+type DashboardCardProps = {
+ icon: string | ReactNode;
+ title: string;
+ description: string;
+ href: string;
};
-export function ListRoadmaps(props: ListRoadmapsProps) {
- const { roadmaps } = props;
- const [showAll, setShowAll] = useState(roadmaps.length <= 12);
- const roadmapsToShow = showAll ? roadmaps : roadmaps.slice(0, 12);
-
- const [isMounted, setIsMounted] = useState(false);
- useEffect(() => {
- setIsMounted(true);
- }, []);
+function DashboardCard(props: DashboardCardProps) {
+ const { icon, title, description, href } = props;
return (
-
-
+
+
{title}
+
{description}
+
+
);
}
diff --git a/src/components/Dashboard/RecommendedRoadmaps.tsx b/src/components/Dashboard/RecommendedRoadmaps.tsx
new file mode 100644
index 000000000..9b587ea73
--- /dev/null
+++ b/src/components/Dashboard/RecommendedRoadmaps.tsx
@@ -0,0 +1,92 @@
+import { useEffect, useState } from 'react';
+import type { BuiltInRoadmap } from './PersonalDashboard';
+import { MarkFavorite } from '../FeaturedItems/MarkFavorite';
+
+type RecommendedRoadmapsProps = {
+ roadmaps: BuiltInRoadmap[];
+ isLoading: boolean;
+};
+
+export function RecommendedRoadmaps(props: RecommendedRoadmapsProps) {
+ const { roadmaps, isLoading } = props;
+
+ const [showAll, setShowAll] = useState(false);
+ const roadmapsToShow = showAll ? roadmaps : roadmaps.slice(0, 12);
+
+ const [isMounted, setIsMounted] = useState(false);
+
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ useEffect(() => {
+ setShowAll(roadmaps.length < 12);
+ }, [roadmaps]);
+
+ return (
+ <>
+
+ Recommended Roadmaps
+
+
+ {isLoading ? (
+
+ {Array.from({ length: 12 }).map((_, index) => (
+
+ ))}
+
+ ) : (
+
+
+ {roadmapsToShow.map((roadmap) => (
+
+ ))}
+
+
+ {!showAll && (
+
+
+
+ )}
+
+ )}
+ >
+ );
+}
+
+function RecommendedCardSkeleton() {
+ return (
+
+ );
+}
diff --git a/src/lib/date.ts b/src/lib/date.ts
index 2adbc3a86..45b0b25c2 100644
--- a/src/lib/date.ts
+++ b/src/lib/date.ts
@@ -65,3 +65,15 @@ export function formatActivityDate(date: string): string {
day: 'numeric',
});
}
+
+export function getCurrentPeriod() {
+ const now = new Date();
+ const hour = now.getHours();
+ if (hour < 12) {
+ return 'morning';
+ } else if (hour < 18) {
+ return 'afternoon';
+ } else {
+ return 'evening';
+ }
+}
diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro
index 01e2749fb..242e651a6 100644
--- a/src/pages/dashboard.astro
+++ b/src/pages/dashboard.astro
@@ -18,6 +18,7 @@ const enrichedRoleRoadmaps = roleRoadmaps
url: `/${roadmap.id}`,
title: frontmatter.briefTitle,
description: frontmatter.briefDescription,
+ relatedRoadmapIds: frontmatter.relatedRoadmaps,
};
});
const enrichedSkillRoadmaps = skillRoadmaps
@@ -31,6 +32,7 @@ const enrichedSkillRoadmaps = skillRoadmaps
title:
frontmatter.briefTitle === 'Go' ? 'Go Roadmap' : frontmatter.briefTitle,
description: frontmatter.briefDescription,
+ relatedRoadmapIds: frontmatter.relatedRoadmaps,
};
});