Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('App Component', () => {

// Check if navigation elements are present
expect(screen.getAllByText('About').length).toBeGreaterThan(0)
expect(screen.getAllByText('Partners').length).toBeGreaterThan(0)
expect(screen.getAllByText('Verticals').length).toBeGreaterThan(0)
expect(screen.getAllByText('News & Updates').length).toBeGreaterThan(0)
})

Expand Down
33 changes: 0 additions & 33 deletions src/__tests__/home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,37 +46,4 @@ describe('Home page', () => {
renderHome(false)
expect(screen.getByText(/Built for Developers,/i)).toBeInTheDocument()
})

describe('Partners section', () => {
it('renders the Partners section heading', () => {
renderHome(false)
const heading = screen.getByRole('heading', {
level: 2,
name: /Our Partners/i,
})
expect(heading).toBeInTheDocument()
})

it('renders the Partners section tagline', () => {
renderHome(false)
expect(
screen.getByText(/Building a foundation of trust for every industry/i)
).toBeInTheDocument()
})

it('renders the View All Partners button', () => {
renderHome(false)
expect(
screen.getByRole('button', { name: /View All Partners/i })
).toBeInTheDocument()
})

it('renders at least one partner logo', () => {
renderHome(false)
const imgs = screen.getAllByRole('img', {
name: 'Institute of Technical Education',
})
expect(imgs[0]).toBeInTheDocument()
})
})
})
68 changes: 66 additions & 2 deletions src/components/common/Navbar/Navbar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { render, screen, fireEvent, within } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import Navbar from './Navbar'

Expand Down Expand Up @@ -37,10 +37,74 @@ describe('Navbar Component', () => {
)
// home remove from navbar
expect(screen.getAllByText('About').length).toBeGreaterThan(0)
expect(screen.getAllByText('Partners').length).toBeGreaterThan(0)
expect(screen.getAllByText('Verticals').length).toBeGreaterThan(0)
expect(screen.getAllByText('News & Updates').length).toBeGreaterThan(0)
})

it('opens the Verticals dropdown with external links', () => {
renderWithRouter(
<Navbar isDarkMode={false} setIsDarkMode={mockSetIsDarkMode} />
)

fireEvent.click(
screen.getByRole('button', { name: /toggle verticals menu/i })
)

const menu = screen.getByRole('menu', { name: /verticals menu/i })
expect(menu).toBeInTheDocument()
expect(
screen.getByRole('menuitem', { name: 'TradeTrust' })
).toHaveAttribute('href', 'https://tradetrust.io/')
expect(screen.getByRole('menuitem', { name: 'OpenCerts' })).toHaveAttribute(
'href',
'https://opencerts.io/'
)
expect(screen.getByRole('menuitem', { name: 'SAL' })).toHaveAttribute(
'href',
'https://legalisation.sal.sg/'
)
})

it('selecting a mobile Verticals link closes both mobile and desktop menus', () => {
renderWithRouter(
<Navbar isDarkMode={false} setIsDarkMode={mockSetIsDarkMode} />
)

// Open the mobile menu via the hamburger button
const hamburgerButton = screen
.getAllByRole('button')
.find(btn => btn.querySelector('svg path[d*="M20.1694 16.75"]'))
fireEvent.click(hamburgerButton!)

const mobileMenu = screen.getByRole('navigation', {
name: /mobile navigation menu/i,
})

// Open the Verticals submenu from within the mobile menu
fireEvent.click(
within(mobileMenu).getByRole('button', {
name: /toggle verticals menu/i,
})
)

const mobileLink = within(mobileMenu).getByText('TradeTrust')

// mousedown must NOT close the submenu (clicks inside the mobile
// container are contained), or the link unmounts before click fires
fireEvent.mouseDown(mobileLink)
expect(within(mobileMenu).getByText('TradeTrust')).toBeInTheDocument()

fireEvent.click(mobileLink)

// Selecting the link closes both the mobile menu and the dropdown
expect(
screen.queryByRole('navigation', { name: /mobile navigation menu/i })
).not.toBeInTheDocument()
expect(
screen.queryByRole('menu', { name: /verticals menu/i })
).not.toBeInTheDocument()
})

it('renders Contact Us button', () => {
renderWithRouter(
<Navbar isDarkMode={false} setIsDarkMode={mockSetIsDarkMode} />
Expand Down
161 changes: 146 additions & 15 deletions src/components/common/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@ interface NavbarProps {
setIsDarkMode: Dispatch<SetStateAction<boolean>>
}

const VERTICAL_LINKS = [
{ label: 'TradeTrust', href: 'https://tradetrust.io/' },
{ label: 'OpenCerts', href: 'https://opencerts.io/' },
{ label: 'SAL', href: 'https://legalisation.sal.sg/' },
]

const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [_isEcosystemOpen, setIsEcosystemOpen] = useState(false)
const [isVerticalsOpen, setIsVerticalsOpen] = useState(false)
const navRef = useRef<HTMLElement>(null)
const verticalsRef = useRef<HTMLDivElement>(null)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const mobileMenuRef = useRef<HTMLDivElement>(null)
const location = useLocation()
const isSettingsActive = location.pathname.startsWith('/settings')
const isNewsActive = location.pathname.startsWith('/news-updates')
const isPartnersActive = location.pathname.startsWith('/partners')
const isAboutActive = location.pathname.startsWith('/about')

useEffect(() => {
Expand All @@ -26,11 +34,20 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
) {
setIsMobileMenuOpen(false)
setIsEcosystemOpen(false)
setIsVerticalsOpen(false)
}
if (
isVerticalsOpen &&
verticalsRef.current &&
!verticalsRef.current.contains(e.target as Node) &&
!mobileMenuRef.current?.contains(e.target as Node)
) {
setIsVerticalsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [isMobileMenuOpen])
}, [isMobileMenuOpen, isVerticalsOpen])

return (
<nav
Expand All @@ -48,6 +65,7 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
setIsMobileMenuOpen(!isMobileMenuOpen)
if (isMobileMenuOpen) {
setIsEcosystemOpen(false)
setIsVerticalsOpen(false)
}
}}
aria-label="Toggle mobile menu"
Expand Down Expand Up @@ -125,9 +143,13 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
</Link>
</div>

<div className="p-2">
<Link
to="/partners"
<div className="p-2 relative" ref={verticalsRef}>
<button
type="button"
onClick={() => setIsVerticalsOpen(!isVerticalsOpen)}
aria-label="Toggle verticals menu"
aria-expanded={isVerticalsOpen}
aria-haspopup="menu"
className="min-w-[40px] min-h-[40px] flex items-center justify-center px-1 py-[5px] rounded-lg transition-colors duration-200"
style={{ backgroundColor: 'transparent' }}
onMouseEnter={e => {
Expand All @@ -140,9 +162,9 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
}}
>
<div
className="px-1 py-1 text-center text-sm font-bold font-urbanist leading-snug"
className="px-1 py-1 flex items-center gap-1 text-center text-sm font-bold font-urbanist leading-snug"
style={{
color: isPartnersActive
color: isVerticalsOpen
? isDarkMode
? '#7D80D7'
: '#5B5BB3'
Expand All @@ -151,9 +173,67 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
: '#5B6571',
}}
>
Partners
Verticals
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`transition-transform duration-200 ${isVerticalsOpen ? 'rotate-180' : ''}`}
>
<path
d="M6 9L12 15L18 9"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</Link>
</button>
{isVerticalsOpen && (
<div
role="menu"
aria-label="Verticals menu"
className="absolute top-full left-0 mt-1 min-w-[180px] rounded-lg border shadow-lg py-2 z-50"
style={{
backgroundColor: isDarkMode
? 'rgba(30, 32, 38, 0.98)'
: 'rgba(255, 255, 255, 0.98)',
borderColor: isDarkMode
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.1)',
backdropFilter: 'blur(8px)',
}}
>
{VERTICAL_LINKS.map(({ label, href }) => (
<a
key={label}
role="menuitem"
href={href}
target="_blank"
rel="noopener noreferrer"
onClick={() => setIsVerticalsOpen(false)}
className="block px-4 py-2 text-sm font-bold font-urbanist transition-colors duration-200"
style={{
color: isDarkMode ? '#808894' : '#5B6571',
backgroundColor: 'transparent',
}}
onMouseEnter={e => {
e.currentTarget.style.backgroundColor = isDarkMode
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.05)'
}}
onMouseLeave={e => {
e.currentTarget.style.backgroundColor = 'transparent'
}}
>
{label}
</a>
))}
</div>
)}
</div>

{/* Ecosystem and Gallery temporarily removed - restore from git */}
Expand Down Expand Up @@ -258,6 +338,7 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
{/* Mobile Menu Dropdown */}
{isMobileMenuOpen && (
<div
ref={mobileMenuRef}
role="navigation"
aria-label="Mobile navigation menu"
className="lg:hidden absolute top-[88px] left-0 right-0 border-t shadow-lg"
Expand Down Expand Up @@ -296,11 +377,14 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
>
About
</Link>
<Link
to="/partners"
className="px-4 py-3 text-left text-sm font-bold font-urbanist rounded-lg transition-colors duration-200"
<button
type="button"
onClick={() => setIsVerticalsOpen(!isVerticalsOpen)}
aria-label="Toggle verticals menu"
aria-expanded={isVerticalsOpen}
className="px-4 py-3 flex items-center justify-between text-left text-sm font-bold font-urbanist rounded-lg transition-colors duration-200"
style={{
color: isPartnersActive
color: isVerticalsOpen
? isDarkMode
? '#7D80D7'
: '#5B5BB3'
Expand All @@ -318,8 +402,55 @@ const Navbar = ({ isDarkMode, setIsDarkMode: _setIsDarkMode }: NavbarProps) => {
e.currentTarget.style.backgroundColor = 'transparent'
}}
>
Partners
</Link>
Verticals
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`transition-transform duration-200 ${isVerticalsOpen ? 'rotate-180' : ''}`}
>
<path
d="M6 9L12 15L18 9"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
{isVerticalsOpen && (
<div className="flex flex-col">
{VERTICAL_LINKS.map(({ label, href }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
onClick={() => {
setIsVerticalsOpen(false)
setIsMobileMenuOpen(false)
}}
className="pl-8 pr-4 py-3 text-left text-sm font-bold font-urbanist rounded-lg transition-colors duration-200"
style={{
color: isDarkMode ? '#808894' : '#5B6571',
backgroundColor: 'transparent',
}}
onMouseEnter={e => {
e.currentTarget.style.backgroundColor = isDarkMode
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.05)'
}}
onMouseLeave={e => {
e.currentTarget.style.backgroundColor = 'transparent'
}}
>
{label}
</a>
))}
</div>
)}
{/* Ecosystem and Gallery temporarily removed - restore from git */}
<Link
to="/news-updates"
Expand Down
2 changes: 0 additions & 2 deletions src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import HeroSection from '../../components/home/HeroSection'
import VerifySection from '../../components/home/VerifySection'
import Carousel from '../../components/home/Carousel'
import BuiltForDev from '../../components/home/BuiltForDev'
import PartnersSection from '../../components/home/PartnersSection'

interface HomeProps {
isDarkMode: boolean
Expand All @@ -16,7 +15,6 @@ const Home = ({ isDarkMode }: HomeProps) => {
<VerifySection isDarkMode={isDarkMode} />
<Carousel isDarkMode={isDarkMode} />
<BuiltForDev isDarkMode={isDarkMode} />
<PartnersSection isDarkMode={isDarkMode} />
</div>
</div>
)
Expand Down
Loading
Loading