From 3f72a1712597fc902c7a9c69179df3627a76b92b Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Tue, 29 Sep 2026 19:57:34 +0800 Subject: [PATCH 01/12] feat(analytics): create initial Google Analytics component --- src/components/analytics/google-analytics.tsx | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/components/analytics/google-analytics.tsx diff --git a/src/components/analytics/google-analytics.tsx b/src/components/analytics/google-analytics.tsx new file mode 100644 index 0000000..83638dc --- /dev/null +++ b/src/components/analytics/google-analytics.tsx @@ -0,0 +1,3 @@ +export default function GoogleAnalytics() { + return
Insert Google Analytics here.
; +} From 88e50758efc5fd5bf853951a5719f1ed8b4fea08 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Wed, 30 Sep 2026 03:16:04 +0800 Subject: [PATCH 02/12] feat(analytics): add Google Analytics tracking scripts --- src/components/analytics/google-analytics.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/analytics/google-analytics.tsx b/src/components/analytics/google-analytics.tsx index 83638dc..a9ca735 100644 --- a/src/components/analytics/google-analytics.tsx +++ b/src/components/analytics/google-analytics.tsx @@ -1,3 +1,22 @@ +import Script from 'next/script'; + export default function GoogleAnalytics() { - return
Insert Google Analytics here.
; + const GA_ID = process.env.NEXT_PUBLIC_GA_ID; + + return ( + <> + + + ); } From 87a218250a99e1652be807052b3ac4e450bac9a3 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Wed, 30 Sep 2026 03:17:08 +0800 Subject: [PATCH 03/12] feat(analytics): import GoogleAnalytics component to site layout --- src/app/layout.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 48981da..ab8b5b1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,10 @@ import type { Metadata, Viewport } from 'next'; import { Sora } from 'next/font/google'; +import { cn } from '@/lib/utils'; import './globals.css'; import ThemeProvider from '@/components/providers/theme-provider'; -import { cn } from '@/lib/utils'; import SkipLink from '@/components/accessibility/skip-link'; +import GoogleAnalytics from '@/components/analytics/google-analytics'; const sora = Sora({ subsets: ['latin'], display: 'swap' }); const metaDescription = @@ -96,6 +97,7 @@ export default function RootLayout({ > + {children} From 90f82bf44db096ad141c60714a5528c00ec23423 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 16:26:48 +0800 Subject: [PATCH 04/12] feat(meta): update metadata for the experience page --- src/app/experience/page.tsx | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/app/experience/page.tsx b/src/app/experience/page.tsx index dd7fdbd..93e49d7 100644 --- a/src/app/experience/page.tsx +++ b/src/app/experience/page.tsx @@ -2,8 +2,32 @@ import Breadcrumb from '@/components/sections/breadcrumb'; import Footer from '@/components/sections/footer'; import ThemeToggle from '@/components/theme/theme-toggle'; import { WORK_EXPERIENCES } from '@/data/experiences'; -import Link from 'next/link'; import { FiExternalLink } from 'react-icons/fi'; +import type { Metadata } from 'next'; +import Link from 'next/link'; + +const metaDescription = + 'Professional experience and roles of Elmar Chavez as a full stack developer.'; +export const metadata: Metadata = { + title: 'Elmar Chavez | Experience', + description: metaDescription, + keywords: [ + 'Elmar Chavez Experience', + 'Elmar Chavez Developer', + 'Full Stack Developer Experience', + 'Web Developer Experience', + 'Software Developer Experience', + ], + openGraph: { + title: 'Experience | Elmar Chavez', + description: metaDescription, + }, + twitter: { + card: 'summary_large_image', + title: 'Experience | Elmar Chavez', + description: metaDescription, + }, +}; export default function ExperiencePage() { return ( From d1fbb40126d78be36b1e4cff4c5fe4a0ed0cac9e Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 16:30:55 +0800 Subject: [PATCH 05/12] feat(meta): update metadata for the certificates page --- src/app/certificates/page.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/app/certificates/page.tsx b/src/app/certificates/page.tsx index 55445f8..9d2583d 100644 --- a/src/app/certificates/page.tsx +++ b/src/app/certificates/page.tsx @@ -4,8 +4,32 @@ import Footer from '@/components/sections/footer'; import CertificateImage from '@/components/ui/certificate-image'; import { CERTIFICATES } from '@/data/experiences'; import { FiExternalLink } from 'react-icons/fi'; +import type { Metadata } from 'next'; import Link from 'next/link'; +const metaDescription = + 'Professional certifications and credentials earned by Elmar Chavez as a full stack developer.'; +export const metadata: Metadata = { + title: 'Elmar Chavez | Certificates', + description: metaDescription, + keywords: [ + 'Elmar Chavez Certificates', + 'Elmar Chavez Certifications', + 'Web Development Certifications', + 'Full Stack Developer Certifications', + 'Software Development Certifications', + ], + openGraph: { + title: 'Certificates | Elmar Chavez', + description: metaDescription, + }, + twitter: { + card: 'summary_large_image', + title: 'Certificates | Elmar Chavez', + description: metaDescription, + }, +}; + export default function ExperiencePage() { return (
Date: Thu, 1 Oct 2026 17:11:31 +0800 Subject: [PATCH 06/12] feat(home): update Experience component section --- src/components/sections/experience.tsx | 84 +++++++------------------- 1 file changed, 22 insertions(+), 62 deletions(-) diff --git a/src/components/sections/experience.tsx b/src/components/sections/experience.tsx index 3327071..fb21a12 100644 --- a/src/components/sections/experience.tsx +++ b/src/components/sections/experience.tsx @@ -1,70 +1,30 @@ -import { FiExternalLink } from 'react-icons/fi'; -import { workExperiences, certificates } from '@/data/experiences'; +import { workExperiences } from '@/data/experiences'; import ViewAllLink from '../ui/view-all-link'; -import Link from 'next/link'; const Experience = () => { return ( -
-
-
-

Experience

- -
+
+
+

Experience

+ +
-
    - {workExperiences.map( - ({ id, startDate, endDate, position, companyName, location }) => { - return ( -
  • -

    {position}

    -

    {`${companyName} · ${location}`}

    -

    {`${startDate} - ${endDate}`}

    -
  • - ); - }, - )} -
-
- -
-
-

Certificates

- -
- -
    - {certificates.map( - ({ id, title, institution, dateReceived, urlLink }) => { - return ( -
  • - - {title} - - -

    - {institution} - - {`Issued ${dateReceived}`} - -

    -
  • - ); - }, - )} -
-
+
    + {workExperiences.map( + ({ id, startDate, endDate, position, companyName, location }) => { + return ( +
  • +

    {position}

    +

    {`${companyName} · ${location}`}

    +

    {`${startDate} - ${endDate}`}

    +
  • + ); + }, + )} +
); }; From fdd4372303cbed1c86ec333d1d4d349785120983 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:12:13 +0800 Subject: [PATCH 07/12] feat(home): create Certificates component section to separate it from Experience --- src/components/sections/certificates.tsx | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/sections/certificates.tsx diff --git a/src/components/sections/certificates.tsx b/src/components/sections/certificates.tsx new file mode 100644 index 0000000..6c91b1b --- /dev/null +++ b/src/components/sections/certificates.tsx @@ -0,0 +1,44 @@ +import { certificates } from '@/data/experiences'; +import { FiExternalLink } from 'react-icons/fi'; +import ViewAllLink from '../ui/view-all-link'; +import Link from 'next/link'; + +export default function Certificates() { + return ( +
+
+

Certificates

+ +
+ +
    + {certificates.map( + ({ id, title, institution, dateReceived, urlLink }) => { + return ( +
  • + + {title} + + +

    + {institution} + + {`Issued ${dateReceived}`} + +

    +
  • + ); + }, + )} +
+
+ ); +} From da3a602bba7d158f11b32d9c40645ff57a2d31ca Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:13:07 +0800 Subject: [PATCH 08/12] feat(home): update flex layout to Experience and Certificates section --- src/app/page.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 608e50c..f6af79e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,6 @@ import About from '@/components/sections/about'; import Blog from '@/components/sections/blog'; +import Certificates from '@/components/sections/certificates'; import Experience from '@/components/sections/experience'; import Footer from '@/components/sections/footer'; import Hero from '@/components/sections/hero'; @@ -14,8 +15,11 @@ export default function Home() { -
- +
+
+ + +
From 7d7842a7c64601fbe06540a4a92428c1c778a429 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:13:59 +0800 Subject: [PATCH 09/12] test(certificates): migrate and update component testing for Certificates section --- src/components/sections/certificates.test.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/components/sections/certificates.test.tsx diff --git a/src/components/sections/certificates.test.tsx b/src/components/sections/certificates.test.tsx new file mode 100644 index 0000000..a225a82 --- /dev/null +++ b/src/components/sections/certificates.test.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@testing-library/react'; +import { certificates } from '@/data/experiences'; +import Certificates from './certificates'; + +describe('Certificates section', () => { + test('renders Certificates heading', () => { + render(); + const title = screen.getByRole('heading', { name: /certificates/i }); + expect(title).toBeInTheDocument(); + }); + test('renders every certificate', () => { + render(); + const certificates = screen.getAllByRole('listitem'); + expect(certificates).toHaveLength(certificates.length); + }); + test('renders clickable certificate links', () => { + render(); + const links = screen.getAllByRole('link', { name: /certificate/i }); + expect(links).toHaveLength(certificates.length); + }); +}); From aa8fe3dadfc8f686d6d1139486be3dc10389cfe5 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:14:27 +0800 Subject: [PATCH 10/12] test(experience): update component testing for Experience section --- src/components/sections/experience.test.tsx | 29 +++------------------ 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/components/sections/experience.test.tsx b/src/components/sections/experience.test.tsx index e3a4fe4..36eeb1b 100644 --- a/src/components/sections/experience.test.tsx +++ b/src/components/sections/experience.test.tsx @@ -1,5 +1,5 @@ -import { render, screen, within } from '@testing-library/react'; -import { workExperiences, certificates } from '@/data/experiences'; +import { render, screen } from '@testing-library/react'; +import { workExperiences } from '@/data/experiences'; import Experience from './experience'; describe('Experience section', () => { @@ -8,30 +8,9 @@ describe('Experience section', () => { const title = screen.getByRole('heading', { name: /experience/i }); expect(title).toBeInTheDocument(); }); - test('renders Certificates heading', () => { - render(); - const title = screen.getByRole('heading', { name: /certificates/i }); - expect(title).toBeInTheDocument(); - }); test('renders every work experience', () => { render(); - const title = screen.getByRole('heading', { name: /experience/i }); - const article = title.closest('article')!; - const experienceSection = within(article); - const experiencesRendered = experienceSection.getAllByRole('listitem'); - expect(experiencesRendered).toHaveLength(workExperiences.length); - }); - test('renders every certificate', () => { - render(); - const title = screen.getByRole('heading', { name: /certificate/i }); - const article = title.closest('article')!; - const certificateSection = within(article); - const certificatesRendered = certificateSection.getAllByRole('listitem'); - expect(certificatesRendered).toHaveLength(certificates.length); - }); - test('renders clickable certificate links', () => { - render(); - const links = screen.getAllByRole('link', { name: /certificate/i }); - expect(links).toHaveLength(certificates.length); + const experiences = screen.getAllByRole('listitem'); + expect(experiences).toHaveLength(workExperiences.length); }); }); From d9b9231697374d6704eede760b1221a51bbb75c1 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:20:57 +0800 Subject: [PATCH 11/12] chore(git): exclude .env.example from .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 63bcfdd..32c1bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel From 5106d8f16cbe3da68585591529ec2bd26d8337a4 Mon Sep 17 00:00:00 2001 From: Elmar Chavez Date: Thu, 1 Oct 2026 17:21:58 +0800 Subject: [PATCH 12/12] chore(env): create .env.example for Google Analytics ID --- .env.example | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2d95e70 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# Insert Google Analytics Tag ID here +NEXT_PUBLIC_GA_ID= \ No newline at end of file