add certifications route
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import EmailLink from "./EmailLink";
|
||||
import { PROFILES } from "../utils/site";
|
||||
|
||||
export default function Header() {
|
||||
@@ -18,8 +17,8 @@ export default function Header() {
|
||||
<Link to="/projects" className="hover:underline">
|
||||
Projects
|
||||
</Link>
|
||||
<Link to="/sports" className="hover:underline">
|
||||
Sports
|
||||
<Link to="/certifications" className="hover:underline">
|
||||
Certifications
|
||||
</Link>
|
||||
<a
|
||||
href={PROFILES.github}
|
||||
@@ -32,7 +31,6 @@ export default function Header() {
|
||||
<a href={PROFILES.blog} rel="me" className="hover:underline">
|
||||
Blog
|
||||
</a>
|
||||
<EmailLink className="hover:underline" />
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -14,11 +14,25 @@ export default function ProjectCard({ project }: { project: Project }) {
|
||||
<div className="bg-neutral-800 rounded-lg p-2 border border-white">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h3 className="text-base font-bold">
|
||||
<Link to={`/projects/${project.slug}`} className="hover:underline">
|
||||
<Link
|
||||
to={
|
||||
!project.isCert
|
||||
? `/projects/${project.slug}`
|
||||
: `/certifications/${project.slug}`
|
||||
}
|
||||
className="hover:underline"
|
||||
>
|
||||
{project.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<Link to={`/projects/${project.slug}`} className="shrink-0">
|
||||
<Link
|
||||
to={
|
||||
!project.isCert
|
||||
? `/projects/${project.slug}`
|
||||
: `/certifications/${project.slug}`
|
||||
}
|
||||
className="shrink-0"
|
||||
>
|
||||
<button className="bg-gray-500 border border-zinc-500 rounded-lg px-2 py-1 text-sm whitespace-nowrap">
|
||||
View >
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,7 @@ export type Project = {
|
||||
pills: string[];
|
||||
/** paragraphs of prose for the detail page */
|
||||
body: string[];
|
||||
isCert?: boolean;
|
||||
};
|
||||
|
||||
export const PROJECTS: Project[] = [
|
||||
@@ -65,6 +66,36 @@ export const PROJECTS: Project[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const CERTIFICATIONS: Project[] = [
|
||||
{
|
||||
slug: "foundational-csharp",
|
||||
title: "Foundational C# (Microsoft)",
|
||||
description:
|
||||
"Foundational C# course created by Microsoft, issued by FreeCodeCamp",
|
||||
link: "https://www.freecodecamp.org/certification/alexavf/foundational-c-sharp-with-microsoft",
|
||||
pills: ["Software Development", "C#"],
|
||||
body: [
|
||||
"A course created by Microsoft, a comprehensive intro to the core concepts and the syntax of C#.",
|
||||
"Worked with creating basic, logical console applications and how to work with data and properly debug using Visual Studio.",
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "introduction-to-frontend-development",
|
||||
title: "Intro to Frontend Development (Meta)",
|
||||
description: "Intro to frontend created by Meta, issued by Coursera",
|
||||
link: "https://coursera.org/share/df3426a13f7bcdaa9f82f88e77554425",
|
||||
pills: ["Software Development", "JavaScript", "HTML", "CSS"],
|
||||
body: [
|
||||
"A course that took me through the basics of frontend development with JS, CSS, HTML and more.",
|
||||
"Currently working on the full certification from Meta, including experience with React, Git, Figma and more.",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function findProject(slug: string | undefined) {
|
||||
return PROJECTS.find((p) => p.slug === slug);
|
||||
}
|
||||
|
||||
export function findCertification(slug: string | undefined) {
|
||||
return CERTIFICATIONS.find((p) => p.slug === slug);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import Footer from "../components/Footer";
|
||||
import NotFound from "./NotFound";
|
||||
import { findCertification } from "../data/projects";
|
||||
|
||||
export default function CertificationDetail() {
|
||||
const { slug } = useParams();
|
||||
const cert = findCertification(slug);
|
||||
|
||||
if (!cert) return <NotFound />;
|
||||
|
||||
return (
|
||||
<main className="my-12 sm:my-20">
|
||||
<nav aria-label="Breadcrumb" className="py-2">
|
||||
<p className="opacity-85 text-sm text-left">
|
||||
<Link to="/certifications" className="hover:underline">
|
||||
CERTIFICATIONS
|
||||
</Link>
|
||||
</p>
|
||||
</nav>
|
||||
|
||||
<div className="py-2">
|
||||
<h1 className="text-3xl sm:text-4xl text-balance py-2">{cert.title}</h1>
|
||||
<div className="flex flex-wrap gap-2 py-2">
|
||||
{cert.pills.map((pill) => (
|
||||
<div
|
||||
key={pill}
|
||||
className="bg-zinc-500 rounded-lg text-xs p-1 font-semibold"
|
||||
>
|
||||
{pill}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2 space-y-6">
|
||||
{cert.body.map((paragraph, i) => (
|
||||
<p key={i}>{paragraph}</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="py-4">
|
||||
<a
|
||||
href={cert.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-lime-300 hover:underline"
|
||||
>
|
||||
View the certificate >
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<p className="text-sm">
|
||||
<Link to="/certifications" className="underline">
|
||||
< All certifications
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import ProjectCard from "../components/ProjectCard";
|
||||
import Footer from "../components/Footer";
|
||||
import { CERTIFICATIONS } from "../data/projects";
|
||||
|
||||
export default function Certifications() {
|
||||
return (
|
||||
<main className="my-12 sm:my-20">
|
||||
<div className="py-2">
|
||||
<p className="opacity-85 text-sm text-left">CERTIFICATIONS</p>
|
||||
<h1 className="text-3xl sm:text-4xl text-balance py-2">
|
||||
Professional Certifications
|
||||
</h1>
|
||||
<p className="py-2">
|
||||
All of my personal professional certifications for different things in
|
||||
the software dev /QA testing space.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<section aria-labelledby="all-certifications">
|
||||
<h2
|
||||
id="all-certifications"
|
||||
className="opacity-85 text-sm text-left pb-2"
|
||||
>
|
||||
ALL CERTIFICATIONS
|
||||
</h2>
|
||||
<div className="my-2 space-y-2">
|
||||
{CERTIFICATIONS.map((project) => (
|
||||
<ProjectCard
|
||||
key={project.slug}
|
||||
project={{ ...project, isCert: true }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<p className="text-sm">
|
||||
<Link to="/" className="underline">
|
||||
< Back home
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
+18
-1
@@ -7,7 +7,7 @@ import BlogRow from "../components/BlogRow";
|
||||
import GameComponent from "../components/info/GameComponent";
|
||||
import MusicComponent from "../components/info/MusicComponent";
|
||||
import EmailLink from "../components/EmailLink";
|
||||
import { PROJECTS } from "../data/projects";
|
||||
import { PROJECTS, CERTIFICATIONS } from "../data/projects";
|
||||
import { PROFILES } from "../utils/site";
|
||||
|
||||
export default function Home() {
|
||||
@@ -84,6 +84,23 @@ export default function Home() {
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<section aria-labelledby="certifications">
|
||||
<h2 id="certifications" className="opacity-85 text-sm text-left pb-2">
|
||||
CERTIFICATIONS
|
||||
</h2>
|
||||
|
||||
<div className="my-2 space-y-2">
|
||||
{CERTIFICATIONS.map((project) => (
|
||||
<ProjectCard
|
||||
key={project.slug}
|
||||
project={{ ...project, isCert: true }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr className="my-4 border-zinc-800" />
|
||||
|
||||
<section aria-labelledby="my-activities">
|
||||
<h2 id="my-activities" className="opacity-85 text-sm text-left pb-2">
|
||||
MY ACTIVITIES
|
||||
|
||||
+40
-2
@@ -5,13 +5,22 @@ import ProjectDetail from "./pages/ProjectDetail";
|
||||
import Sports from "./pages/Sports";
|
||||
import Pictures from "./pages/Pictures";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import { PROJECTS, findProject } from "./data/projects";
|
||||
import {
|
||||
CERTIFICATIONS,
|
||||
PROJECTS,
|
||||
findProject,
|
||||
findCertification,
|
||||
} from "./data/projects";
|
||||
import type { PageMeta } from "./utils/seo";
|
||||
import {
|
||||
profilePageGraph,
|
||||
projectListGraph,
|
||||
projectGraph,
|
||||
certificationGraph,
|
||||
certificationListGraph,
|
||||
} from "./utils/structuredData";
|
||||
import Certifications from "./pages/Certifications";
|
||||
import CertificationDetail from "./pages/CertificationDetail";
|
||||
|
||||
type Params = Record<string, string | undefined>;
|
||||
|
||||
@@ -78,6 +87,35 @@ export const routes: AppRoute[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/certifications",
|
||||
element: <Certifications />,
|
||||
sitemapPaths: () => ["/certifications"],
|
||||
meta: () => ({
|
||||
title: "Certifications — Alex Frantz",
|
||||
description:
|
||||
"All of my professionally-issued certifications. From software development to software testing, I am always studying something.",
|
||||
path: "/certifications",
|
||||
jsonLd: certificationListGraph(PROJECTS),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "/certifications/:slug",
|
||||
element: <CertificationDetail />,
|
||||
sitemapPaths: () => CERTIFICATIONS.map((p) => `/certifications/${p.slug}`),
|
||||
meta: (params) => {
|
||||
const certification = findCertification(params.slug);
|
||||
if (!certification) return null;
|
||||
|
||||
return {
|
||||
title: `${certification.title} — Alex Frantz`,
|
||||
description: certification.description,
|
||||
path: `/certifications/${certification.slug}`,
|
||||
type: "article",
|
||||
jsonLd: certificationGraph(certification),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/sports",
|
||||
element: <Sports />,
|
||||
@@ -85,7 +123,7 @@ export const routes: AppRoute[] = [
|
||||
meta: () => ({
|
||||
title: "Bills, Sabres & Yankees Scores — Alex Frantz",
|
||||
description:
|
||||
"Live Buffalo Bills, Buffalo Sabres and New York Yankees scores, plus how my fantasy football matchup is going this week.",
|
||||
"Live Buffalo Bills and Buffalo Sabres scores, plus how my fantasy football matchup is going this week.",
|
||||
path: "/sports",
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -119,3 +119,52 @@ export function projectGraph(project: Project): object[] {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function certificationListGraph(certifications: Project[]): object[] {
|
||||
return [
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
url: absoluteUrl("/projects"),
|
||||
name: "Projects",
|
||||
about: { "@id": PERSON_ID },
|
||||
mainEntity: {
|
||||
"@type": "ItemList",
|
||||
itemListElement: certifications.map((project, index) => ({
|
||||
"@type": "ListItem",
|
||||
position: index + 1,
|
||||
url: absoluteUrl(`/certifications/${project.slug}`),
|
||||
name: project.title,
|
||||
})),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function certificationGraph(certificate: Project): object[] {
|
||||
return [
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareSourceCode",
|
||||
name: certificate.title,
|
||||
description: certificate.description,
|
||||
url: absoluteUrl(`/projects/${certificate.slug}`),
|
||||
codeRepository: certificate.link,
|
||||
programmingLanguage: certificate.pills,
|
||||
author: { "@id": PERSON_ID },
|
||||
},
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 1,
|
||||
name: "Projects",
|
||||
item: absoluteUrl("/certifications"),
|
||||
},
|
||||
{ "@type": "ListItem", position: 2, name: certificate.title },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user