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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,42 @@ describe('Footer', () => {
expect(internalLinks.length).toBeGreaterThan(0);
});

it('renders footer link groups in the expected order', () => {
renderWithNextIntl(<Footer />);

expect(screen.getByRole('heading', { name: 'Todd' })).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: 'Terms & Policies' })
).toBeInTheDocument();

expect(
screen
.getAllByRole('link')
.filter((link) =>
[
'Research',
'About',
'Careers',
'News',
'Terms of Use',
'Privacy Policy',
'Accessibility',
'Legal',
].includes(link.textContent ?? '')
)
.map((link) => link.textContent)
).toEqual([
'Research',
'About',
'Careers',
'News',
'Terms of Use',
'Privacy Policy',
'Accessibility',
'Legal',
]);
});

it('has social media links with accessible names via aria-label', () => {
renderWithNextIntl(<Footer />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import CookiePreferencesModal from '@/components/common/cookie-preferences-modal
import ToddHeader from '@/components/common/wordmark/todd-wordmark';
import { Link } from '@/i18n/config';
import { useLocale, useTranslations } from 'next-intl';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
import {
Expand All @@ -25,30 +24,6 @@ const Footer = () => {
const t = useTranslations('footer');
const tCookiePreferences = useTranslations('cookiePreferences');

const siteLinks = [
{ href: '/about', label: t('links.whoWeAre') },
{ href: '/research', label: t('links.whatWeDo') },
{ href: '/news', label: t('links.news') },
{ href: '/careers', label: t('links.careers') },
{ href: '/contact', label: t('links.contact') },
// comment out until we have a foundation page
// { href: '/foundation', label: t('links.foundation') },
];

const legalLinks = [
{ href: '/terms', label: t('links.terms') },
{ href: '/privacy', label: t('links.privacy') },
{
href: '/accessibility',
label: t('links.accessibility'),
},
{ href: '/legal', label: t('links.legal') },
{
href: 'https://toddagriscience.safebase.us/',
label: t('links.vulnReporting'),
},
];

const socialMediaIcons = [
{
icon: <FaInstagram aria-hidden="true" />,
Expand All @@ -72,11 +47,33 @@ const Footer = () => {
},
];

const footerSections = [
{
title: t('sections.todd'),
links: [
{ href: '/research', label: t('links.research') },
{ href: '/about', label: t('links.about') },
{ href: '/careers', label: t('links.careers') },
{ href: '/news', label: t('links.news') },
],
},
{
title: t('sections.termsAndPolicies'),
links: [
{ href: '/terms', label: t('links.terms') },
{ href: '/privacy', label: t('links.privacy') },
{ href: '/accessibility', label: t('links.accessibility') },
{ href: '/legal', label: t('links.legal') },
],
},
];

const currentYear = new Date().getFullYear();

const pathname = usePathname() || '/';
const [langOpen, setLangOpen] = useState(false);
const [pendingLocale, setPendingLocale] = useState<string | null>(null);
const currentLanguageLabel = locale === 'es' ? 'Español' : 'English';

const trimPath = (p: string) => p.replace(/\/+$/, '') || '/';

Expand All @@ -101,62 +98,67 @@ const Footer = () => {
}, [pendingLocale, pathname]);

return (
<footer className="bg-background text-foreground font-light mt-8 mb-8 px-4 py-10 sm:mb-0 md:px-6 lg:px-12 xl:px-18">
<footer className="bg-background text-foreground font-normal mt-8 mb-8 px-4 py-10 sm:mb-0 md:px-6 lg:px-12 xl:px-18">
<div className="flex flex-col md:flex-row">
<div className="flex w-full flex-col justify-between md:mb-16 md:flex-row">
<ToddHeader className="md:text-4xl lg:text-5xl" localeAware />
<div className="flex flex-row gap-32 max-md:mt-8 md:ml-auto">
<div className="flex flex-col gap-4">
{siteLinks.slice(0, 4).map((val) => (
<Link
key={val.href}
href={val.href}
locale={locale}
className="text-base md:text-base"
>
<span className="link text-underline-left text-underline-left-black text-black">
{val.label}
</span>
</Link>
))}
</div>

<div className="flex flex-col gap-4">
{siteLinks.slice(4, 8).map((val) => (
<Link
key={val.href}
href={val.href}
locale={locale}
className="text-base md:text-base"
>
<span className="link text-underline-left text-underline-left-black text-black">
{val.label}
</span>
</Link>
))}
</div>
<div className="grid grid-cols-1 gap-y-8 max-md:mt-8 sm:grid-cols-2 sm:gap-x-16 md:ml-auto md:mr-20 md:gap-x-32">
{footerSections.map((section) => (
<div key={section.title} className="flex flex-col gap-4">
<h2 className="text-[15px] font-normal text-foreground/50">
{section.title}
</h2>
{section.links.map((val) => (
<Link
key={val.href}
href={val.href}
locale={locale}
className="text-[15px] font-normal"
>
<span className="link text-underline-left text-underline-left-black text-black">
{val.label}
</span>
</Link>
))}
</div>
))}
</div>
</div>
</div>
<div className="mt-8 flex flex-col gap-10">
<div className="flex flex-row gap-4">
<div className="-mx-4 mt-8 flex flex-col gap-6 border-t-[1.5px] border-foreground/10 px-4 pt-6 md:-mx-6 md:flex-row md:items-center md:justify-between md:px-6 lg:-mx-12 lg:px-12 xl:-mx-18 xl:px-18">
<div className="flex flex-col text-[15px] font-normal md:flex-row md:items-center md:gap-10">
<p className="text-[15px] font-normal">
Todd Agriscience © 2018-{currentYear}
</p>
<CookiePreferencesModal
trigger={
<span className="text-[15px] font-normal underline underline-offset-4">
{tCookiePreferences('manageCookies')}
</span>
}
/>
</div>
<div className="flex flex-wrap items-center gap-8 md:justify-end">
{socialMediaIcons.map((val) => (
<Link key={val.href} href={val.href} aria-label={val.ariaLabel}>
{val.icon}
</Link>
))}
<div className="relative inline-flex">
<button
type="button"
aria-haspopup="menu"
aria-expanded={langOpen}
onClick={() => setLangOpen(!langOpen)}
className="flex flex-row items-center gap-4 cursor-pointer focus:outline-none focus:ring-0"
className="flex flex-row items-center gap-3 text-[15px] font-normal cursor-pointer focus:outline-none focus:ring-0 md:gap-6"
aria-label="Change language"
>
<Image
src="/united_states_flag.svg"
alt=""
width={50}
height={50}
className="h-6 w-6"
/>
<span>{t('location')}</span>
<span className="text-[15px] font-normal">
{currentLanguageLabel}
</span>
<span className="text-[15px] font-normal text-foreground/50">
{t('location')}
</span>
</button>

{langOpen && (
Expand All @@ -169,18 +171,18 @@ const Footer = () => {
/>
<div
role="menu"
className="absolute left-0 bottom-full mb-2 w-full rounded bg-white p-1 text-black shadow-lg z-20"
className="absolute right-0 bottom-full mb-2 w-40 rounded bg-white p-1 text-black shadow-lg z-20"
>
<button
onClick={() => handleLocaleChange('en')}
className="block w-full px-3 py-2 text-left text-sm cursor-pointer hover:bg-gray-100"
className="block w-full px-3 py-2 text-left text-sm font-normal cursor-pointer hover:bg-gray-100"
role="menuitem"
>
English
</button>
<button
onClick={() => handleLocaleChange('es')}
className="block w-full px-3 py-2 text-left text-sm cursor-pointer hover:bg-gray-100"
className="block w-full px-3 py-2 text-left text-sm font-normal cursor-pointer hover:bg-gray-100"
role="menuitem"
>
Español
Expand All @@ -190,34 +192,6 @@ const Footer = () => {
)}
</div>
</div>
<p>© Todd Agriscience {currentYear}</p>
<div className="flex flex-col flex-wrap gap-6 items-start md:flex-row">
{legalLinks.map((val) => (
<Link key={val.href} href={val.href} locale={locale}>
{val.label}
</Link>
))}
<CookiePreferencesModal
trigger={
<div className="flex items-center gap-2">
<span>{tCookiePreferences('managePreferences')}</span>
<Image
alt=""
src={'/privacyoptions.svg'}
width={29}
height={14}
/>
</div>
}
/>
</div>
<div className="flex flex-row flex-wrap gap-6">
{socialMediaIcons.map((val) => (
<Link key={val.href} href={val.href} aria-label={val.ariaLabel}>
{val.icon}
</Link>
))}
</div>
</div>
</footer>
);
Expand Down
3 changes: 2 additions & 1 deletion apps/site/src/messages/cookiePreferences/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"toggleDescription": "These activities may be considered “sales” or “sharing” under applicable US state laws. You may opt-out of these non-essential activities by using the toggle below or by visiting our website with a legally-recognized opt-out preference signal enabled, such as the Global Privacy Control (GPC). Note that requests to opt-out of these activities are unique to the browser you are using. You will need to renew your opt-out choice if you visit with another browser or device, or if you clear your cookies.",
"save": "Confirm",
"cancel": "Cancel",
"managePreferences": "Your Privacy Choices"
"managePreferences": "Your Privacy Choices",
"manageCookies": "Manage Cookies"
}
}
3 changes: 2 additions & 1 deletion apps/site/src/messages/cookiePreferences/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"toggleDescription": "Estas actividades pueden considerarse “ventas” o “compartición” según las leyes estatales aplicables de EE. UU. Puedes optar por no participar en estas actividades no esenciales usando el interruptor a continuación o visitando nuestro sitio web con una señal de preferencia de exclusión legalmente reconocida habilitada, como Global Privacy Control (GPC). Ten en cuenta que las solicitudes para excluirte de estas actividades son únicas para el navegador que estás utilizando. Deberás renovar tu elección de exclusión si visitas el sitio con otro navegador o dispositivo, o si borras tus cookies.",
"save": "Confirmar",
"cancel": "Cancelar",
"managePreferences": "Tus opciones de privacidad"
"managePreferences": "Tus opciones de privacidad",
"manageCookies": "Administrar cookies"
}
}
9 changes: 6 additions & 3 deletions apps/site/src/messages/footer/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
"sections": {
"todd": "Todd",
"connect": "Connect",
"legal": "Legal"
"legal": "Legal",
"termsAndPolicies": "Terms & Policies"
},
"links": {
"about": "About",
"research": "Research",
"whoWeAre": "Who We Are",
"whatWeDo": "What We Do",
"news": "News",
Expand All @@ -17,8 +20,8 @@
"sponsorships": "Sponsorships",
"foundation": "Foundation",
"accessibility": "Accessibility",
"privacy": "Privacy policy",
"terms": "Terms & Conditions",
"privacy": "Privacy Policy",
"terms": "Terms of Use",
"vulnReporting": "Vulnerability reporting",
"legal": "Legal"
},
Expand Down
7 changes: 5 additions & 2 deletions apps/site/src/messages/footer/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
"sections": {
"todd": "Todd",
"connect": "Conéctate",
"legal": "Legal"
"legal": "Legal",
"termsAndPolicies": "Terms & Policies"
},
"links": {
"about": "Acerca de",
"research": "Investigación",
"whoWeAre": "Quiénes Somos",
"whatWeDo": "Qué Hacemos",
"news": "Noticias",
Expand All @@ -18,7 +21,7 @@
"foundation": "Fundación",
"accessibility": "Accesibilidad",
"privacy": "Política de Privacidad",
"terms": "Términos & Condiciones",
"terms": "Términos de Uso",
"vulnReporting": "Reporte de Vulnerabilidades",
"legal": "Legal"
},
Expand Down
Loading