From a408abbe30ec79fb72b2e69f7a5b9de80b6612f7 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Fri, 30 Aug 2024 01:22:35 +0600 Subject: [PATCH] feat: personal dashboard page --- src/components/Dashboard/DashboardPage.tsx | 50 ++++-- src/components/Dashboard/DashboardTab.tsx | 14 +- .../Dashboard/PersonalDashboard.tsx | 151 ++++++++++++++++-- src/components/Dashboard/TeamDashboard.tsx | 36 ++--- src/pages/dashboard.astro | 30 +++- 5 files changed, 229 insertions(+), 52 deletions(-) diff --git a/src/components/Dashboard/DashboardPage.tsx b/src/components/Dashboard/DashboardPage.tsx index 11f100cf3..e03829635 100644 --- a/src/components/Dashboard/DashboardPage.tsx +++ b/src/components/Dashboard/DashboardPage.tsx @@ -5,12 +5,19 @@ import { useStore } from '@nanostores/react'; import { $teamList } from '../../stores/team'; import type { TeamListResponse } from '../TeamDropdown/TeamDropdown'; import { DashboardTab } from './DashboardTab'; -import { PersonalDashboard } from './PersonalDashboard'; +import { PersonalDashboard, type BuiltInRoadmap } from './PersonalDashboard'; import { TeamDashboard } from './TeamDashboard'; +import { getUser } from '../../lib/jwt'; -type DashboardPageProps = {}; +type DashboardPageProps = { + builtInRoadmaps?: BuiltInRoadmap[]; + builtInBestPractices?: BuiltInRoadmap[]; +}; export function DashboardPage(props: DashboardPageProps) { + const { builtInRoadmaps, builtInBestPractices } = props; + + const currentUser = getUser(); const toast = useToast(); const teamList = useStore($teamList); @@ -37,13 +44,18 @@ export function DashboardPage(props: DashboardPageProps) { getAllTeams().finally(() => setIsLoading(false)); }, []); + const userAvatar = currentUser?.avatar + ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${currentUser.avatar}` + : '/images/default-avatar.png'; + return ( -
+
setSelectedTeamId(undefined)} + avatar={userAvatar} /> {isLoading && ( <> @@ -55,14 +67,21 @@ export function DashboardPage(props: DashboardPageProps) { {!isLoading && ( <> - {teamList.map((team) => ( - setSelectedTeamId(team._id)} - /> - ))} + {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 && ( + + )} {selectedTeamId && }
); @@ -81,6 +105,6 @@ export function DashboardPage(props: DashboardPageProps) { function DashboardTabLoading() { return ( -
+
); } diff --git a/src/components/Dashboard/DashboardTab.tsx b/src/components/Dashboard/DashboardTab.tsx index 96204e264..0895fe107 100644 --- a/src/components/Dashboard/DashboardTab.tsx +++ b/src/components/Dashboard/DashboardTab.tsx @@ -7,10 +7,12 @@ type DashboardTabProps = { onClick?: () => void; className?: string; href?: string; + avatar?: string; + icon?: ReactNode; }; export function DashboardTab(props: DashboardTabProps) { - const { isActive, onClick, label, className, href } = props; + const { isActive, onClick, label, className, href, avatar, icon } = props; const Slot = href ? 'a' : 'button'; @@ -18,12 +20,20 @@ export function DashboardTab(props: DashboardTabProps) { + {avatar && ( + avatar + )} + {icon} {label} ); diff --git a/src/components/Dashboard/PersonalDashboard.tsx b/src/components/Dashboard/PersonalDashboard.tsx index f8f100aa6..e03cf8ee7 100644 --- a/src/components/Dashboard/PersonalDashboard.tsx +++ b/src/components/Dashboard/PersonalDashboard.tsx @@ -7,15 +7,31 @@ import { ProjectProgress } from '../Activity/ProjectProgress'; import type { PageType } from '../CommandMenu/CommandMenu'; import { useToast } from '../../hooks/use-toast'; import { LoadingProgress } from './LoadingProgress'; +import { ArrowUpRight, Pencil } from 'lucide-react'; type UserDashboardResponse = { + name: string; + email: string; + avatar: string; + headline: string; + username: string; progresses: UserProgress[]; projects: ProjectStatusDocument[]; }; -type PersonalDashboardProps = {}; +export type BuiltInRoadmap = { + id: string; + title: string; + description: string; +}; + +type PersonalDashboardProps = { + builtInRoadmaps?: BuiltInRoadmap[]; + builtInBestPractices?: BuiltInRoadmap[]; +}; export function PersonalDashboard(props: PersonalDashboardProps) { + const { builtInRoadmaps = [], builtInBestPractices = [] } = props; const toast = useToast(); const [isLoading, setIsLoading] = useState(true); @@ -79,19 +95,81 @@ export function PersonalDashboard(props: PersonalDashboardProps) { }); const enrichedProjects = - personalDashboardDetails?.projects?.map((project) => { - const projectDetail = projectDetails.find( - (page) => page.id === project.projectId, - ); + 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 { - ...project, - title: projectDetail?.title || 'N/A', - }; - }) || []; + return 0; + }) || []; + + const { avatar, name, headline, email, username } = + personalDashboardDetails || {}; + const avatarLink = avatar + ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${avatar}` + : '/images/default-avatar.png'; return (
+ {isLoading && ( +
+ )} + {!isLoading && ( +
+
+
+ {name} +
+
+

{name}

+

{headline || email}

+
+
+ + +
+ )} +

Progress and Bookmarks

@@ -141,6 +219,59 @@ export function PersonalDashboard(props: PersonalDashboardProps) { })}
)} + +

+ All Roadmaps +

+ + +

+ Best Practices +

+
); } + +type ListRoadmapsProps = { + roadmaps: BuiltInRoadmap[]; +}; +export function ListRoadmaps(props: ListRoadmapsProps) { + const { roadmaps } = props; + + const [showAll, setShowAll] = useState(roadmaps.length <= 12); + const roadmapsToShow = showAll ? roadmaps : roadmaps.slice(0, 12); + + return ( +
+
+ {roadmapsToShow.map((roadmap) => ( + + {roadmap.title} + + ))} +
+ + {!showAll && ( +
+ +
+ )} +
+ ); +} diff --git a/src/components/Dashboard/TeamDashboard.tsx b/src/components/Dashboard/TeamDashboard.tsx index 4c36c51e9..1f9e617b8 100644 --- a/src/components/Dashboard/TeamDashboard.tsx +++ b/src/components/Dashboard/TeamDashboard.tsx @@ -5,7 +5,6 @@ import { useToast } from '../../hooks/use-toast'; import { getUser } from '../../lib/jwt'; import { LoadingProgress } from './LoadingProgress'; import { ResourceProgress } from '../Activity/ResourceProgress'; -import { Plus, Minus } from 'lucide-react'; import { TeamActivityPage } from '../TeamActivity/TeamActivityPage'; import { cn } from '../../lib/classname'; @@ -21,7 +20,6 @@ export function TeamDashboard(props: TeamDashboardProps) { const [isLoading, setIsLoading] = useState(true); const [teamMembers, setTeamMembers] = useState([]); - const [showAllMembers, setShowAllMembers] = useState(false); async function getTeamProgress() { const { response, error } = await httpGet( @@ -65,19 +63,17 @@ export function TeamDashboard(props: TeamDashboardProps) { (progress) => progress.resourceType === 'roadmap', ) || []; - const allMembersWithoutCurrentUser = teamMembers - .sort((a, b) => { - if (a.email === currentUser.email) { - return -1; - } + const allMembersWithoutCurrentUser = teamMembers.sort((a, b) => { + if (a.email === currentUser.email) { + return -1; + } - if (b.email === currentUser.email) { - return 1; - } + if (b.email === currentUser.email) { + return 1; + } - return 0; - }) - .slice(0, showAllMembers ? teamMembers.length : 15); + return 0; + }); return (
@@ -137,18 +133,6 @@ export function TeamDashboard(props: TeamDashboardProps) { ); })} - {teamMembers.length > 0 && ( - - )}
)} @@ -166,7 +150,7 @@ function TeamMemberLoading(props: TeamMemberLoadingProps) { return (
- {Array.from({ length: 8 }).map((_, index) => ( + {Array.from({ length: 15 }).map((_, index) => (
{ + const { frontmatter } = roadmap; + + return { + id: roadmap.id, + title: frontmatter.briefTitle, + description: frontmatter.briefDescription, + }; +}); +const enrichedBestPractices = bestPractices.map((bestPractice) => { + const { frontmatter } = bestPractice; + + return { + id: bestPractice.id, + title: frontmatter.briefTitle, + description: frontmatter.briefDescription, + }; +}); --- - +