Empty screen handling

pull/8189/head
Kamran Ahmed 3 months ago
parent d7f74b34a6
commit 94231874b0
  1. 5
      src/components/Dashboard/PersonalDashboard.tsx
  2. 69
      src/components/HeroSection/FavoriteRoadmaps.tsx
  3. 16
      src/components/HeroSection/HeroItemsGroup.tsx
  4. 8
      src/components/HeroSection/HeroTitle.tsx

@ -299,7 +299,10 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
<div className="relative mt-6 border-t border-t-[#1e293c] pt-12"> <div className="relative mt-6 border-t border-t-[#1e293c] pt-12">
<div className="container"> <div className="container">
<h2 className="text-md font-regular absolute -top-[17px] flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2"> <h2
id="role-based-roadmaps"
className="text-md font-regular absolute -top-[17px] flex rounded-lg border border-[#1e293c] bg-slate-900 px-3 py-1 text-slate-400 sm:left-1/2 sm:-translate-x-1/2"
>
Role Based Roadmaps Role Based Roadmaps
</h2> </h2>

@ -5,6 +5,8 @@ import {
Sparkle, Sparkle,
Eye, Eye,
EyeOff, EyeOff,
Square,
SquareCheckBig,
} from 'lucide-react'; } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx'; import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
@ -14,6 +16,7 @@ import { HeroProject } from './HeroProject';
import { HeroRoadmap } from './HeroRoadmap'; import { HeroRoadmap } from './HeroRoadmap';
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx'; import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx';
import { HeroItemsGroup } from './HeroItemsGroup'; import { HeroItemsGroup } from './HeroItemsGroup';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
export type AIRoadmapType = { export type AIRoadmapType = {
id: string; id: string;
@ -34,6 +37,7 @@ type FavoriteRoadmapsProps = {
export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) { export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
const { progress, isLoading, customRoadmaps, aiRoadmaps, projects } = props; const { progress, isLoading, customRoadmaps, aiRoadmaps, projects } = props;
const [showCompleted, setShowCompleted] = useState(false); const [showCompleted, setShowCompleted] = useState(false);
const [isCreatingCustomRoadmap, setIsCreatingCustomRoadmap] = useState(false);
const completedProjects = projects.filter( const completedProjects = projects.filter(
(project) => project.submittedAt && project.repositoryUrl, (project) => project.submittedAt && project.repositoryUrl,
@ -49,10 +53,31 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
{isCreatingCustomRoadmap && (
<CreateRoadmapModal
onClose={() => {
setIsCreatingCustomRoadmap(false);
}}
/>
)}
<HeroItemsGroup <HeroItemsGroup
icon={<CheckIcon additionalClasses="mr-1.5 h-[14px] w-[14px]" />} icon={<CheckIcon additionalClasses="mr-1.5 h-[14px] w-[14px]" />}
isLoading={isLoading} isLoading={isLoading}
title="Your progress and bookmarks" title="Your progress and bookmarks"
isEmpty={progress.length === 0}
emptyTitle={
<>
No bookmarked roadmaps yet
<a
href="#role-based-roadmaps"
className="ml-1.5 inline-flex items-center gap-1 font-medium text-blue-500 underline-offset-2 hover:underline"
>
<SquareCheckBig className="size-3.5" strokeWidth={2.5} />
Bookmark a roadmap
</a>
</>
}
> >
{progress.map((resource) => ( {progress.map((resource) => (
<HeroRoadmap <HeroRoadmap
@ -71,13 +96,27 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
} }
/> />
))} ))}
<CreateRoadmapButton />
</HeroItemsGroup> </HeroItemsGroup>
<HeroItemsGroup <HeroItemsGroup
icon={<MapIcon className="mr-1.5 h-[14px] w-[14px]" />} icon={<MapIcon className="mr-1.5 h-[14px] w-[14px]" />}
isLoading={isLoading} isLoading={isLoading}
title="Your custom roadmaps" title="Your custom roadmaps"
isEmpty={customRoadmaps.length === 0}
emptyTitle={
<>
No custom roadmaps found
<button
onClick={() => {
setIsCreatingCustomRoadmap(true);
}}
className="ml-1.5 inline-flex items-center gap-1 font-medium text-blue-500 underline-offset-2 hover:underline"
>
<SquareCheckBig className="size-3.5" strokeWidth={2.5} />
Create custom roadmap
</button>
</>
}
> >
{customRoadmaps.map((customRoadmap) => ( {customRoadmaps.map((customRoadmap) => (
<HeroRoadmap <HeroRoadmap
@ -101,6 +140,21 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
icon={<Sparkle className="mr-1.5 h-[14px] w-[14px]" />} icon={<Sparkle className="mr-1.5 h-[14px] w-[14px]" />}
isLoading={isLoading} isLoading={isLoading}
title="Your AI roadmaps" title="Your AI roadmaps"
isEmpty={aiRoadmaps.length === 0}
emptyTitle={
<>
No AI roadmaps found
<button
onClick={() => {
setIsCreatingCustomRoadmap(true);
}}
className="ml-1.5 inline-flex items-center gap-1 font-medium text-blue-500 underline-offset-2 hover:underline"
>
<SquareCheckBig className="size-3.5" strokeWidth={2.5} />
Generate AI roadmap
</button>
</>
}
> >
{aiRoadmaps.map((aiRoadmap) => ( {aiRoadmaps.map((aiRoadmap) => (
<HeroRoadmap <HeroRoadmap
@ -130,6 +184,19 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
icon={<FolderKanban className="mr-1.5 h-[14px] w-[14px]" />} icon={<FolderKanban className="mr-1.5 h-[14px] w-[14px]" />}
isLoading={isLoading} isLoading={isLoading}
title="Your active projects" title="Your active projects"
isEmpty={projectsToShow.length === 0}
emptyTitle={
<>
No active projects found
<a
href="/projects"
className="ml-1.5 inline-flex items-center gap-1 font-medium text-blue-500 underline-offset-2 hover:underline"
>
<SquareCheckBig className="size-3.5" strokeWidth={2.5} />
Start a new project
</a>
</>
}
rightContent={ rightContent={
completedProjects.length > 0 && ( completedProjects.length > 0 && (
<button <button

@ -5,6 +5,8 @@ import { HeroTitle } from './HeroTitle';
type HeroItemsGroupProps = { type HeroItemsGroupProps = {
icon: any; icon: any;
isLoading?: boolean; isLoading?: boolean;
isEmpty?: boolean;
emptyTitle?: ReactNode;
title: string | ReactNode; title: string | ReactNode;
rightContent?: ReactNode; rightContent?: ReactNode;
children?: ReactNode; children?: ReactNode;
@ -15,14 +17,14 @@ export function HeroItemsGroup(props: HeroItemsGroupProps) {
const { const {
icon, icon,
isLoading = false, isLoading = false,
isEmpty = false,
emptyTitle,
title, title,
rightContent, rightContent,
children, children,
className, className,
} = props; } = props;
const isInitialRender = useRef(true);
const storageKey = `hero-group-${title}-collapsed`; const storageKey = `hero-group-${title}-collapsed`;
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
@ -36,15 +38,15 @@ export function HeroItemsGroup(props: HeroItemsGroupProps) {
setIsCollapsed(isCollapsedByStorage()); setIsCollapsed(isCollapsedByStorage());
}, [isLoading]); }, [isLoading]);
const isLoadingOrCollapsed = isLoading || isCollapsed; const isLoadingOrCollapsedOrEmpty = isLoading || isCollapsed || isEmpty;
return ( return (
<div <div
className={cn( className={cn(
'border-b border-gray-800/50', 'border-b border-gray-800/50',
{ {
'py-4': !isLoadingOrCollapsed, 'py-4': !isLoadingOrCollapsedOrEmpty,
'py-3': isLoadingOrCollapsed, 'py-3': isLoadingOrCollapsedOrEmpty,
'opacity-50 transition-opacity hover:opacity-100': 'opacity-50 transition-opacity hover:opacity-100':
isCollapsed && !isLoading, isCollapsed && !isLoading,
}, },
@ -55,6 +57,8 @@ export function HeroItemsGroup(props: HeroItemsGroupProps) {
<HeroTitle <HeroTitle
icon={icon} icon={icon}
isLoading={isLoading} isLoading={isLoading}
isEmpty={isEmpty}
emptyTitle={emptyTitle}
title={title} title={title}
rightContent={rightContent} rightContent={rightContent}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
@ -63,7 +67,7 @@ export function HeroItemsGroup(props: HeroItemsGroupProps) {
localStorage.setItem(storageKey, (!isCollapsed).toString()); localStorage.setItem(storageKey, (!isCollapsed).toString());
}} }}
/> />
{!isLoadingOrCollapsed && ( {!isLoadingOrCollapsedOrEmpty && (
<div className="mt-4 grid grid-cols-1 gap-2.5 sm:grid-cols-2 md:grid-cols-3"> <div className="mt-4 grid grid-cols-1 gap-2.5 sm:grid-cols-2 md:grid-cols-3">
{children} {children}
</div> </div>

@ -10,6 +10,8 @@ type HeroTitleProps = {
rightContent?: ReactNode; rightContent?: ReactNode;
isCollapsed?: boolean; isCollapsed?: boolean;
onToggleCollapse?: () => void; onToggleCollapse?: () => void;
isEmpty?: boolean;
emptyTitle?: ReactNode;
}; };
export function HeroTitle(props: HeroTitleProps) { export function HeroTitle(props: HeroTitleProps) {
@ -20,6 +22,8 @@ export function HeroTitle(props: HeroTitleProps) {
rightContent, rightContent,
isCollapsed = false, isCollapsed = false,
onToggleCollapse, onToggleCollapse,
isEmpty = false,
emptyTitle,
} = props; } = props;
return ( return (
@ -32,13 +36,13 @@ export function HeroTitle(props: HeroTitleProps) {
<Spinner /> <Spinner />
</span> </span>
)} )}
{title} {!isEmpty ? title : emptyTitle || title}
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!isCollapsed && rightContent} {!isCollapsed && rightContent}
{!isLoading && ( {!isLoading && !isEmpty && (
<button <button
onClick={onToggleCollapse} onClick={onToggleCollapse}
className={cn( className={cn(

Loading…
Cancel
Save