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 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 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 (
+ {children} 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() { -
- +
+
+ + +
diff --git a/src/components/analytics/google-analytics.tsx b/src/components/analytics/google-analytics.tsx new file mode 100644 index 0000000..a9ca735 --- /dev/null +++ b/src/components/analytics/google-analytics.tsx @@ -0,0 +1,22 @@ +import Script from 'next/script'; + +export default function GoogleAnalytics() { + const GA_ID = process.env.NEXT_PUBLIC_GA_ID; + + return ( + <> + + + ); +} 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); + }); +}); 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}`} + +

    +
  • + ); + }, + )} +
+
+ ); +} 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); }); }); 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}`}

    +
  • + ); + }, + )} +
); };