From 7a37f3825f97ecf7d228a7e0e167f2dfd24a0030 Mon Sep 17 00:00:00 2001 From: Ang Li-Lian Date: Fri, 20 Mar 2026 16:02:49 +0000 Subject: [PATCH 1/5] feat(nav): restructure navigation with separate courses and projects sections Refactored the navigation component to split courses into two distinct sections: - Added separate "Courses" dropdown for non-project courses - Added new "Projects" dropdown for project-type courses - Moved "Events" link into an "Explore" dropdown alongside Blog and Grants - Updated state management to handle the new navigation structure - Maintained responsive behavior across mobile and desktop views This improves navigation clarity by categorizing content more intuitively and provides better organization for different course types. --- apps/website/src/components/Nav/Nav.tsx | 6 +- .../src/components/Nav/_MobileNavLinks.tsx | 8 +- apps/website/src/components/Nav/_NavLinks.tsx | 102 +++++++++++------- .../src/components/Nav/_ProfileLinks.tsx | 3 +- apps/website/src/components/Nav/utils.ts | 3 +- 5 files changed, 77 insertions(+), 45 deletions(-) diff --git a/apps/website/src/components/Nav/Nav.tsx b/apps/website/src/components/Nav/Nav.tsx index 38713283a..406ffb269 100644 --- a/apps/website/src/components/Nav/Nav.tsx +++ b/apps/website/src/components/Nav/Nav.tsx @@ -24,7 +24,8 @@ export const Nav: React.FC = ({ variant: variantProp }) => { const isOnColoredBackground = variant === 'transparent'; const [expandedSections, setExpandedSections] = useState({ - about: false, + courses: false, + projects: false, explore: false, mobileNav: false, profile: false, @@ -40,7 +41,8 @@ export const Nav: React.FC = ({ variant: variantProp }) => { const handleBreakpointChange = () => { setExpandedSections({ - about: false, + courses: false, + projects: false, explore: false, mobileNav: false, profile: false, diff --git a/apps/website/src/components/Nav/_MobileNavLinks.tsx b/apps/website/src/components/Nav/_MobileNavLinks.tsx index 8a2917677..5e5952d01 100644 --- a/apps/website/src/components/Nav/_MobileNavLinks.tsx +++ b/apps/website/src/components/Nav/_MobileNavLinks.tsx @@ -45,6 +45,8 @@ export const MobileNavLinks: React.FC<{ const onToggleMobileNav = () => { updateExpandedSections({ mobileNav: !expandedSections.mobileNav, + courses: false, + projects: false, explore: false, profile: false, }); @@ -68,7 +70,8 @@ export const MobileNavLinks: React.FC<{ // Close mobile nav when any link is clicked if ((e.target as HTMLElement).tagName === 'A') { updateExpandedSections({ - about: false, + courses: false, + projects: false, explore: false, mobileNav: false, profile: false, @@ -79,7 +82,8 @@ export const MobileNavLinks: React.FC<{ // Also handle keyboard navigation if (e.key === 'Enter' && (e.target as HTMLElement).tagName === 'A') { updateExpandedSections({ - about: false, + courses: false, + projects: false, explore: false, mobileNav: false, profile: false, diff --git a/apps/website/src/components/Nav/_NavLinks.tsx b/apps/website/src/components/Nav/_NavLinks.tsx index e514b05cd..ab100e217 100644 --- a/apps/website/src/components/Nav/_NavLinks.tsx +++ b/apps/website/src/components/Nav/_NavLinks.tsx @@ -36,14 +36,29 @@ export const NavLinks: React.FC<{ const { courses, loading } = useCourses(); const { getPrimaryCourseURL } = usePrimaryCourseURL(); - const navCourses = loading ? [] : [ - ...(courses || []).map((course) => ({ - title: course.title, - url: getPrimaryCourseURL(course.slug), - isNew: course.isNew ?? false, - })), + const allCourses = loading ? [] : (courses || []).map((course) => ({ + title: course.title, + url: getPrimaryCourseURL(course.slug), + isNew: course.isNew ?? false, + type: course.type ?? null, + })); + + const navCourses = [ + ...allCourses.filter((course) => course.type !== 'Project'), { title: 'See upcoming rounds', url: ROUTES.courses.url }, ]; + + const navProjects = [ + ...allCourses.filter((course) => course.type === 'Project'), + { title: 'See upcoming rounds', url: ROUTES.courses.url }, + ]; + + const exploreLinks = [ + { title: 'Events', url: 'https://lu.ma/bluedotevents?utm_source=website&utm_campaign=nav', external: true }, + { title: 'Blog', url: ROUTES.blog.url, external: true }, + { title: 'Grants', url: ROUTES.grants.url }, + ]; + const getLinkClasses = (isCurrentPathValue?: boolean) => { // Mobile drawer always has white background, so always use dark text // Desktop navbar uses white text on colored background, dark text elsewhere @@ -63,49 +78,57 @@ export const NavLinks: React.FC<{
*]:w-fit', className)}> updateExpandedSections({ - about: false, - explore: !expandedSections.explore, + courses: !expandedSections.courses, + projects: false, + explore: false, mobileNav: expandedSections.mobileNav, profile: false, })} - onClose={() => updateExpandedSections({ explore: false })} + onClose={() => updateExpandedSections({ courses: false })} title="Courses" loading={loading} /> - - Events - - - Blog - + updateExpandedSections({ + courses: false, + projects: !expandedSections.projects, + explore: false, + mobileNav: expandedSections.mobileNav, + profile: false, + })} + onClose={() => updateExpandedSections({ projects: false })} + title="Projects" + loading={loading} + /> + updateExpandedSections({ + courses: false, + projects: false, + explore: !expandedSections.explore, + mobileNav: expandedSections.mobileNav, + profile: false, + })} + onClose={() => updateExpandedSections({ explore: false })} + title="Explore" + /> About - - Grants - void; onClose: () => void; title: string; // Optional className?: string; - loading: boolean; + loading?: boolean; }> = ({ expandedSections, isExpanded, @@ -137,7 +160,7 @@ const NavDropdown: React.FC<{ onClose, title, className, - loading, + loading = false, }) => { const dropdownRef = useClickOutside( onClose, @@ -216,12 +239,13 @@ const NavDropdown: React.FC<{ return ( - {/* Add separator before "See upcoming rounds" */} - {link.title === 'See upcoming rounds' && ( + {/* Add separator before footer links */} + {(link.title === 'See upcoming rounds' || link.title === 'See all projects') && (
)} updateExpandedSections({ - about: false, + courses: false, + projects: false, explore: false, mobileNav: false, profile: !expandedSections.profile, diff --git a/apps/website/src/components/Nav/utils.ts b/apps/website/src/components/Nav/utils.ts index 5e263b7c4..4222757ee 100644 --- a/apps/website/src/components/Nav/utils.ts +++ b/apps/website/src/components/Nav/utils.ts @@ -21,7 +21,8 @@ export const DRAWER_CLASSES = (isOpen: boolean, zIndex: typeof DRAWER_Z_DEFAULT ); export type ExpandedSectionsState = { - about: boolean; + courses: boolean; + projects: boolean; explore: boolean; mobileNav: boolean; profile: boolean; From 2e80b6360ed5a7fd034ce5a0adccd9eb67595d26 Mon Sep 17 00:00:00 2001 From: Ang Li-Lian Date: Fri, 20 Mar 2026 16:11:08 +0000 Subject: [PATCH 2/5] feat: separate projects section on courses page with anchor navigation - Add dedicated projects section with visual separator and mobile heading - Update navigation to link directly to projects section via anchor (#projects) - Split course sorting logic to handle courses and projects separately - Enhance breadcrumb menu to show projects as distinct category - Maintain consistent display ordering for both courses and projects --- apps/website/src/components/Nav/_NavLinks.tsx | 2 +- apps/website/src/pages/courses/index.tsx | 133 +++++++++++------- 2 files changed, 82 insertions(+), 53 deletions(-) diff --git a/apps/website/src/components/Nav/_NavLinks.tsx b/apps/website/src/components/Nav/_NavLinks.tsx index ab100e217..1d77fae95 100644 --- a/apps/website/src/components/Nav/_NavLinks.tsx +++ b/apps/website/src/components/Nav/_NavLinks.tsx @@ -50,7 +50,7 @@ export const NavLinks: React.FC<{ const navProjects = [ ...allCourses.filter((course) => course.type === 'Project'), - { title: 'See upcoming rounds', url: ROUTES.courses.url }, + { title: 'See upcoming rounds', url: `${ROUTES.courses.url}#projects` }, ]; const exploreLinks = [ diff --git a/apps/website/src/pages/courses/index.tsx b/apps/website/src/pages/courses/index.tsx index c3e06bd2b..d32bf8699 100644 --- a/apps/website/src/pages/courses/index.tsx +++ b/apps/website/src/pages/courses/index.tsx @@ -48,17 +48,15 @@ const useSortedCourses = () => { }, [courses]); // Sort courses by fixed display order - const sortedCourses = useMemo(() => { - return [...displayedCourses].sort((a, b) => { + const sortByDisplayOrder = (items: Course[]) => { + return [...items].sort((a, b) => { const aIndex = COURSE_DISPLAY_ORDER.indexOf(a.slug); const bIndex = COURSE_DISPLAY_ORDER.indexOf(b.slug); - // If both are in the order list, sort by their position if (aIndex !== -1 && bIndex !== -1) { return aIndex - bIndex; } - // If only one is in the order list, it comes first if (aIndex !== -1) { return -1; } @@ -67,13 +65,16 @@ const useSortedCourses = () => { return 1; } - // If neither is in the order list, sort alphabetically return a.title.localeCompare(b.title); }); - }, [displayedCourses]); + }; + + const sortedCourses = useMemo(() => sortByDisplayOrder(displayedCourses.filter((c) => c.type !== 'Project')), [displayedCourses]); + const sortedProjects = useMemo(() => sortByDisplayOrder(displayedCourses.filter((c) => c.type === 'Project')), [displayedCourses]); return { courses: sortedCourses, + projects: sortedProjects, isLoading: coursesLoading, error, }; @@ -81,14 +82,15 @@ const useSortedCourses = () => { /* Main Page Component */ const CoursesPage = () => { - const { courses: displayedCourses, isLoading, error } = useSortedCourses(); + const { courses: displayedCourses, projects: displayedProjects, isLoading, error } = useSortedCourses(); + const allDisplayed = [...displayedCourses, ...displayedProjects]; return (
AI safety courses with certificates - {displayedCourses.length > 0 && ( + {allDisplayed.length > 0 && (