add certifications route
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import EmailLink from "./EmailLink";
|
|
||||||
import { PROFILES } from "../utils/site";
|
import { PROFILES } from "../utils/site";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
@@ -18,8 +17,8 @@ export default function Header() {
|
|||||||
<Link to="/projects" className="hover:underline">
|
<Link to="/projects" className="hover:underline">
|
||||||
Projects
|
Projects
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/sports" className="hover:underline">
|
<Link to="/certifications" className="hover:underline">
|
||||||
Sports
|
Certifications
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
href={PROFILES.github}
|
href={PROFILES.github}
|
||||||
@@ -32,7 +31,6 @@ export default function Header() {
|
|||||||
<a href={PROFILES.blog} rel="me" className="hover:underline">
|
<a href={PROFILES.blog} rel="me" className="hover:underline">
|
||||||
Blog
|
Blog
|
||||||
</a>
|
</a>
|
||||||
<EmailLink className="hover:underline" />
|
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</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="bg-neutral-800 rounded-lg p-2 border border-white">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<h3 className="text-base font-bold">
|
<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}
|
{project.title}
|
||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</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">
|
<button className="bg-gray-500 border border-zinc-500 rounded-lg px-2 py-1 text-sm whitespace-nowrap">
|
||||||
View >
|
View >
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type Project = {
|
|||||||
pills: string[];
|
pills: string[];
|
||||||
/** paragraphs of prose for the detail page */
|
/** paragraphs of prose for the detail page */
|
||||||
body: string[];
|
body: string[];
|
||||||
|
isCert?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PROJECTS: Project[] = [
|
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) {
|
export function findProject(slug: string | undefined) {
|
||||||
return PROJECTS.find((p) => p.slug === slug);
|
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 GameComponent from "../components/info/GameComponent";
|
||||||
import MusicComponent from "../components/info/MusicComponent";
|
import MusicComponent from "../components/info/MusicComponent";
|
||||||
import EmailLink from "../components/EmailLink";
|
import EmailLink from "../components/EmailLink";
|
||||||
import { PROJECTS } from "../data/projects";
|
import { PROJECTS, CERTIFICATIONS } from "../data/projects";
|
||||||
import { PROFILES } from "../utils/site";
|
import { PROFILES } from "../utils/site";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -84,6 +84,23 @@ export default function Home() {
|
|||||||
|
|
||||||
<hr className="my-4 border-zinc-800" />
|
<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">
|
<section aria-labelledby="my-activities">
|
||||||
<h2 id="my-activities" className="opacity-85 text-sm text-left pb-2">
|
<h2 id="my-activities" className="opacity-85 text-sm text-left pb-2">
|
||||||
MY ACTIVITIES
|
MY ACTIVITIES
|
||||||
|
|||||||
+40
-2
@@ -5,13 +5,22 @@ import ProjectDetail from "./pages/ProjectDetail";
|
|||||||
import Sports from "./pages/Sports";
|
import Sports from "./pages/Sports";
|
||||||
import Pictures from "./pages/Pictures";
|
import Pictures from "./pages/Pictures";
|
||||||
import NotFound from "./pages/NotFound";
|
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 type { PageMeta } from "./utils/seo";
|
||||||
import {
|
import {
|
||||||
profilePageGraph,
|
profilePageGraph,
|
||||||
projectListGraph,
|
projectListGraph,
|
||||||
projectGraph,
|
projectGraph,
|
||||||
|
certificationGraph,
|
||||||
|
certificationListGraph,
|
||||||
} from "./utils/structuredData";
|
} from "./utils/structuredData";
|
||||||
|
import Certifications from "./pages/Certifications";
|
||||||
|
import CertificationDetail from "./pages/CertificationDetail";
|
||||||
|
|
||||||
type Params = Record<string, string | undefined>;
|
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",
|
path: "/sports",
|
||||||
element: <Sports />,
|
element: <Sports />,
|
||||||
@@ -85,7 +123,7 @@ export const routes: AppRoute[] = [
|
|||||||
meta: () => ({
|
meta: () => ({
|
||||||
title: "Bills, Sabres & Yankees Scores — Alex Frantz",
|
title: "Bills, Sabres & Yankees Scores — Alex Frantz",
|
||||||
description:
|
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",
|
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