Repository: Liatoshynsky-Foundation/lf-client
Pull request #139: Implemented Footer navigation links #20
Author: login: irynalaitaruk
Labels: name: frontend
name: feature
Created: 2025-05-29T14:13:16Z | Merged: 2025-05-31T10:32:27Z

### Summary of issue:
Implemented Footer navigation links using next/link.
Styled links and headings.
Added unit tests.

### Result of implementation:
![Screenshot 2025-05-30 at 13 00 11](https://github.com/user-attachments/assets/4b5168d8-526b-49f9-a4c0-58520a989dcd)


![Screenshot 2025-05-30 at 13 24 41](https://github.com/user-attachments/assets/fc3059fa-6785-4a14-830d-30ecc76824d0)








Diff:
diff --git a/app/shared/components/Footer/Footer.tsx b/app/shared/components/Footer/Footer.tsx
index 41366f913..bc89dd7de 100644
--- a/app/shared/components/Footer/Footer.tsx
+++ b/app/shared/components/Footer/Footer.tsx
@@ -3,13 +3,53 @@ import React from 'react';
 import FooterCopyrights from '~/shared/components/Footer/FooterCopyrights/FooterCopyrights';
 import FooterContactInfo from '~/shared/components/Footer/FooterContactInfo/FooterContactInfo';
 import { getTranslations } from 'next-intl/server';
+import FooterNavigation from './FooterNavigation/FooterNavigation';
+
+export const footerData = {
+  text: '© 2025 Liotoshynsky Foundation. Всі права захищені.',
+  links: [
+    { label: 'Політика конфіденційності', href: '/privacy' },
+    { label: 'Умови користування сайтом', href: '/terms' },
+    { label: 'Інформація для медіа / партнерів', href: '/media' }
+  ]
+};
 
 const contacts = {
   title: 'ГРОМАДСЬКА ОРГАНІЗАЦІЯ \n«ФУНДАЦІЯ ЛЯТОШИНСЬКОГО»',
   phone: '067 963 8366',
-  email: 'liatoshynsky@gmail.com',
+  email: 'liatoshynsky@gmail.com'
 };
 
+export const sections = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' }
+    ]
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about-us' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media-about-us' }
+    ]
+  },
+  {
+    title: 'СПІВПРАЦЯ',
+    links: [
+      { label: 'Стати партнером', href: '/become-partner' },
+      { label: 'Наші партнери', href: '/partners' }
+    ]
+  },
+  {
+    title: 'МУЗЕЙ',
+    links: [{ label: 'Кабінет-архів', href: '/museum' }]
+  }
+];
+
 export default async function Footer() {
   const t = await getTranslations('footer');
 
@@ -18,14 +58,15 @@ export default async function Footer() {
     links: [
       { label: t('linkPrivacy'), href: '/privacy' },
       { label: t('linkTerms'), href: '/terms' },
-      { label: t('linkMedia'), href: '/media' },
-    ],
+      { label: t('linkMedia'), href: '/media' }
+    ]
   };
 
   return (
     <Box component="footer">
+      <FooterNavigation sections={sections} />
       <FooterContactInfo contacts={contacts} />
       <FooterCopyrights text={footerData.text} links={footerData.links} />
     </Box>
   );
-}
\ No newline at end of file
+}
diff --git a/app/shared/components/Footer/FooterNavigation/FooterNavigation.styles.ts b/app/shared/components/Footer/FooterNavigation/FooterNavigation.styles.ts
new file mode 100644
index 000000000..30a6b29ef
--- /dev/null
+++ b/app/shared/components/Footer/FooterNavigation/FooterNavigation.styles.ts
@@ -0,0 +1,47 @@
+import { SxProps } from '@mui/material';
+
+export const styles: Record<string, SxProps> = {
+  footer: {
+    width: '100%',
+    color: '#190D03',
+    padding: '24px',
+    display: 'grid',
+    gridTemplateColumns: '1fr',
+    gap: '24px',
+    fontFamily: 'Mulish, sans-serif',
+
+    '@media (min-width: 768px)': {
+      gridTemplateColumns: 'repeat(2, 1fr)',
+      gap: '48px',
+    },
+
+    '@media (min-width: 1024px)': {
+      gridTemplateColumns: 'repeat(4, 1fr)',
+    },
+  },
+  column: {
+    fontSize: '14px',
+  },
+  heading: {
+    fontWeight: 'bold',
+    marginBottom: '8px',
+    fontFamily: 'Mulish, sans-serif',
+  },
+  list: {
+    listStyle: 'none',
+    padding: 0,
+    margin: 0,
+  },
+  link: {
+    textDecoration: 'none',
+    color: 'inherit',
+    fontSize: '16px',
+    fontWeight: 400,
+    lineHeight: 1.5,
+    cursor: 'pointer',
+
+    '&:hover': {
+      textDecoration: 'underline',
+    },
+  },
+};
diff --git a/app/shared/components/Footer/FooterNavigation/FooterNavigation.test.tsx b/app/shared/components/Footer/FooterNavigation/FooterNavigation.test.tsx
new file mode 100644
index 000000000..1b1d9fa6e
--- /dev/null
+++ b/app/shared/components/Footer/FooterNavigation/FooterNavigation.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import FooterNavigation from './FooterNavigation';
+
+describe('FooterNavigation', () => {
+  const mockSections = [
+    {
+      title: 'Section 1',
+      links: [
+        { label: 'Link 1.1', href: '/link-1-1' },
+        { label: 'Link 1.2', href: '/link-1-2' }
+      ]
+    },
+    {
+      title: 'Section 2',
+      links: [
+        { label: 'Link 2.1', href: '/link-2-1' },
+        { label: 'Link 2.2', href: '/link-2-2' }
+      ]
+    }
+  ];
+
+  test('should display all section titles', () => {
+    render(<FooterNavigation sections={mockSections} />);
+
+    mockSections.forEach((section) => {
+      expect(screen.getByText(section.title)).toBeInTheDocument();
+    });
+  });
+
+  test('should display all links with correct labels and hrefs', () => {
+    render(<FooterNavigation sections={mockSections} />);
+
+    mockSections.forEach((section) => {
+      section.links.forEach(({ label, href }) => {
+        const linkElement = screen.getByRole('link', { name: label });
+        expect(linkElement).toBeInTheDocument();
+        expect(linkElement).toHaveAttribute('href', href);
+      });
+    });
+  });
+});
diff --git a/app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx b/app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
new file mode 100644
index 000000000..b93787c52
--- /dev/null
+++ b/app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
@@ -0,0 +1,40 @@
+import React, { FC } from 'react';
+import { Box, Typography, Link } from '@mui/material';
+import { styles } from './FooterNavigation.styles';
+
+type LinkItem = {
+  label: string;
+  href: string;
+};
+
+type Section = {
+  title: string;
+  links: LinkItem[];
+};
+
+interface FooterNavigationProps {
+  sections: Section[];
+}
+
+const FooterNavigation: FC<FooterNavigationProps> = ({ sections }) => {
+  return (
+    <Box sx={styles.footer}>
+      {sections.map((section) => (
+        <Box key={section.title} sx={styles.column}>
+          <Typography sx={styles.heading}>{section.title}</Typography>
+          <Box component="ul" sx={styles.list}>
+            {section.links.map((link) => (
+              <li key={link.label}>
+                <Link href={link.href} sx={styles.link}>
+                  {link.label}
+                </Link>
+              </li>
+            ))}
+          </Box>
+        </Box>
+      ))}
+    </Box>
+  );
+};
+
+export default FooterNavigation;


Reviews:
user: login: IrynaKhylchuk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T14:32:13Z
user: login: IrynaKhylchuk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T14:34:49Z
user: login: Mike-Popovych
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T20:13:20Z
user: login: yur4uwe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T20:29:21Z
user: login: irynalaitaruk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:13:57Z
user: login: irynalaitaruk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:32:51Z
user: login: uliaescha
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T11:16:15Z
user: login: irynalaitaruk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T11:44:00Z
user: login: irynalaitaruk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T11:44:10Z
user: login: VKormylo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T13:03:54Z
user: login: uliaescha
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T17:29:35Z
user: login: bandvov
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-31T04:40:54Z

Review comments:
diff_hunk: @@ -0,0 +1,54 @@
+.footer {
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.module.css
user: login: IrynaKhylchuk
body: We use `.styles.ts` files for styling instead of `.module.css.` Would you mind updating this accordingly?
created_at: 2025-05-29T14:32:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+import Link from 'next/link';
+import styles from './FooterNavigation.module.css';
+
+type LinkItem = {
+  label: string;
+  href: string;
+};
+
+type Section = {
+  title: string;
+  links: LinkItem[];
+};
+
+export const sections: Section[] = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
+    ],
+  },
+  {
+    title: 'СПІВПРАЦЯ',
+    links: [
+      { label: 'Стати партнером', href: '/become-partner' },
+      { label: 'Наші партнери', href: '/partners' },
+    ],
+  },
+  {
+    title: 'МУЗЕЙ',
+    links: [{ label: 'Кабінет-архів', href: '/museum' }],
+  },
+];
+
+const FooterNavigation = () => {
+  return (
+    <Box className={styles.footer}>
+      {sections.map((section) => (
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
user: login: IrynaKhylchuk
body: Consider moving the sections array into a constant outside the component. This way, it won't be recreated on every render, which helps with performance.
created_at: 2025-05-29T14:34:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+import Link from 'next/link';
+import styles from './FooterNavigation.module.css';
+
+type LinkItem = {
+  label: string;
+  href: string;
+};
+
+type Section = {
+  title: string;
+  links: LinkItem[];
+};
+
+export const sections: Section[] = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
+    ],
+  },
+  {
+    title: 'СПІВПРАЦЯ',
+    links: [
+      { label: 'Стати партнером', href: '/become-partner' },
+      { label: 'Наші партнери', href: '/partners' },
+    ],
+  },
+  {
+    title: 'МУЗЕЙ',
+    links: [{ label: 'Кабінет-архів', href: '/museum' }],
+  },
+];
+
+const FooterNavigation = () => {
+  return (
+    <Box className={styles.footer}>
+      {sections.map((section) => (
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
user: login: Mike-Popovych
body: These constants will be replaced with data from the backend in the next sprint, so they will be removed later. It’s better to leave them here for now and delete them once the backend integration is implemented.
created_at: 2025-05-29T20:13:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+import Link from 'next/link';
+import styles from './FooterNavigation.module.css';
+
+type LinkItem = {
+  label: string;
+  href: string;
+};
+
+type Section = {
+  title: string;
+  links: LinkItem[];
+};
+
+export const sections: Section[] = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
+    ],
+  },
+  {
+    title: 'СПІВПРАЦЯ',
+    links: [
+      { label: 'Стати партнером', href: '/become-partner' },
+      { label: 'Наші партнери', href: '/partners' },
+    ],
+  },
+  {
+    title: 'МУЗЕЙ',
+    links: [{ label: 'Кабінет-архів', href: '/museum' }],
+  },
+];
+
+const FooterNavigation = () => {
+  return (
+    <Box className={styles.footer}>
+      {sections.map((section) => (
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
user: login: yur4uwe
body: Maybe better approach would be to pass sections as props and dynamically render them, this way there will be no need to change any code in here
created_at: 2025-05-29T20:29:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+.footer {
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.module.css
user: login: irynalaitaruk
body: @IrynaKhylchuk i've change from '.module.css' to '.styles.ts'
created_at: 2025-05-30T10:13:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+import React from 'react';
+import { Box, Typography } from '@mui/material';
+import Link from 'next/link';
+import styles from './FooterNavigation.module.css';
+
+type LinkItem = {
+  label: string;
+  href: string;
+};
+
+type Section = {
+  title: string;
+  links: LinkItem[];
+};
+
+export const sections: Section[] = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
+    ],
+  },
+  {
+    title: 'СПІВПРАЦЯ',
+    links: [
+      { label: 'Стати партнером', href: '/become-partner' },
+      { label: 'Наші партнери', href: '/partners' },
+    ],
+  },
+  {
+    title: 'МУЗЕЙ',
+    links: [{ label: 'Кабінет-архів', href: '/museum' }],
+  },
+];
+
+const FooterNavigation = () => {
+  return (
+    <Box className={styles.footer}>
+      {sections.map((section) => (
path: app/shared/components/Footer/FooterNavigation/FooterNavigation.tsx
user: login: irynalaitaruk
body: @IrynaKhylchuk @Mike-Popovych @yur4uwe I've added updates and passed sections as props (2 other footer components implemented in the same way)

@IrynaKhylchuk regarding font
![Screenshot 2025-05-30 at 13 00 31](https://github.com/user-attachments/assets/9b870f98-e2a6-4299-a54d-ee75919767c9)

created_at: 2025-05-30T10:32:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,11 +19,42 @@ const contacts = {
   email: 'liatoshynsky@gmail.com',
 };
 
+export const sections = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
path: app/shared/components/Footer/Footer.tsx
user: login: uliaescha
body: Please update the path to the 'About-us' page to `/about-us`, as this route already exists in the app.
created_at: 2025-05-30T11:13:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,11 +19,42 @@ const contacts = {
   email: 'liatoshynsky@gmail.com',
 };
 
+export const sections = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
path: app/shared/components/Footer/Footer.tsx
user: login: uliaescha
body: the same is here `/media-about-us`
created_at: 2025-05-30T11:14:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,11 +19,42 @@ const contacts = {
   email: 'liatoshynsky@gmail.com',
 };
 
+export const sections = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
+      { label: 'Новини', href: '/news' },
+      { label: 'ЗМІ про нас', href: '/media' },
path: app/shared/components/Footer/Footer.tsx
user: login: irynalaitaruk
body: @uliaescha done
created_at: 2025-05-30T11:43:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -18,11 +19,42 @@ const contacts = {
   email: 'liatoshynsky@gmail.com',
 };
 
+export const sections = [
+  {
+    title: 'БОРИС ЛЯТОШИНСЬКИЙ',
+    links: [
+      { label: 'Життєпис', href: '/biography' },
+      { label: 'Творчість', href: '/creativity' },
+      { label: 'Дослідження та наукові роботи', href: '/research' },
+    ],
+  },
+  {
+    title: 'ПРО ФУНДАЦІЮ',
+    links: [
+      { label: 'Про нас', href: '/about' },
path: app/shared/components/Footer/Footer.tsx
user: login: irynalaitaruk
body: @uliaescha done
created_at: 2025-05-30T11:44:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: IrynaKhylchuk
created_at: 2025-05-29T14:33:49Z
body: It looks like the font used in the links doesn't match the design. The Figma file specifies the Mulish font — could you please double-check and apply the correct font family?
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-31T10:26:04Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Liatoshynsky-Foundation_lf-client&pullRequest=139) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=Liatoshynsky-Foundation_lf-client&pullRequest=139&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Liatoshynsky-Foundation_lf-client&pullRequest=139&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Liatoshynsky-Foundation_lf-client&pullRequest=139&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Liatoshynsky-Foundation_lf-client&pullRequest=139&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Liatoshynsky-Foundation_lf-client&pullRequest=139&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Liatoshynsky-Foundation_lf-client&pullRequest=139)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-31T10:28:34Z
body: # ✅  CI Report - 13:28 31/05/2025

## 📝 Lint Report

```
✅ No lint errors found.
```

## 🧪 Test Results

**🔹 Node.js Version:** 22

## **Test Summary:**

```
Test Suites: 28 passed, 28 total
Tests:       99 passed, 99 total
Snapshots:   1 passed, 1 total
Time:        9.816 s
```

```
✅ All tests passed successfully. for Node 22.
```

## 🐳 Docker Health Check

✅ Docker image built successfully


author_association: NONE