Skip to content

Commit

Permalink
feat: add breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
fbuireu committed May 16, 2024
1 parent 72236cf commit 15af4d5
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 69 deletions.
122 changes: 62 additions & 60 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,62 @@
import { defineAction, z, ActionError } from "astro:actions";
import { DEFAULT_LOCALE_STRING } from '../consts.ts';
import { sendEmail } from '@server/email/server.ts';
import { app } from "@server/firebase/server.ts";
import { getFirestore } from "firebase-admin/firestore";
import type { FormData } from '@components/organisms/contactForm';

type ContactDetails = Omit<FormData, "recaptcha">;

const contactFormSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
message: z.string(),
date: z.union([z.date(), z.string()]),
}).omit({ id: true, date: true });

const database = getFirestore(app);

export const server = {
contact: defineAction({
accept: 'form',
input: contactFormSchema,
handler: async ({ name, email, message }: ContactDetails) => {
try {
const contactValidation = contactFormSchema.safeParse({
name,
email,
message
});
if (!contactValidation.success) throw new Error(contactValidation.error?.errors.join(", ") || "Invalid data");

const { data } = contactValidation;
const databaseRef = database.collection("contacts");
await databaseRef.add({
id: crypto.randomUUID(),
name: data.name,
email: data.email,
message: data.message,
date: new Date().toLocaleString(DEFAULT_LOCALE_STRING),
});
const { data: emailData, error: emailError } = await sendEmail(data);
if (emailError && !emailData) {
throw new Error(`Something went wrong sending the email. Error: ${emailError.message} (${emailError.name})`);
}

return {
ok: true
}
} catch (error: unknown) {
const actionError = error as ActionError;

const message = actionError.message || "Something went wrong";
const code = actionError.status ?? 500;

return new ActionError({ code, message });
}
},
}),
};
import { defineAction, z, ActionError } from "astro:actions";
import { DEFAULT_LOCALE_STRING } from "../consts.ts";
import { sendEmail } from "@server/email/server.ts";
import { app } from "@server/firebase/server.ts";
import { getFirestore } from "firebase-admin/firestore";
import type { FormData } from "@components/organisms/contactForm";

type ContactDetails = Omit<FormData, "recaptcha">;

const contactFormSchema = z
.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
message: z.string(),
date: z.union([z.date(), z.string()]),
})
.omit({ id: true, date: true });

const database = getFirestore(app);

export const server = {
contact: defineAction({
accept: "form",
input: contactFormSchema,
handler: async ({ name, email, message }: ContactDetails) => {
try {
const contactValidation = contactFormSchema.safeParse({
name,
email,
message,
});
if (!contactValidation.success) throw new Error(contactValidation.error?.errors.join(", ") || "Invalid data");

const { data } = contactValidation;
const databaseRef = database.collection("contacts");
await databaseRef.add({
id: crypto.randomUUID(),
name: data.name,
email: data.email,
message: data.message,
date: new Date().toLocaleString(DEFAULT_LOCALE_STRING),
});
const { data: emailData, error: emailError } = await sendEmail(data);
if (emailError && !emailData) {
throw new Error(`Something went wrong sending the email. Error: ${emailError.message} (${emailError.name})`);
}

return {
ok: true,
};
} catch (error: unknown) {
const actionError = error as ActionError;

const message = actionError.message || "Something went wrong";
const code = actionError.status ?? 500;

return new ActionError({ code, message });
}
},
}),
};
4 changes: 3 additions & 1 deletion src/pages/about.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import BaseLayout from "@components/templates/baseLayout/BaseLayout.astro";
import AboutIntro from "@components/molecules/aboutIntro/AboutIntro.astro";
import LittleMoreOfMe from "@components/organisms/littleMoreOfMe/LittleMoreOfMe.astro";
import AboutLatestArticles from "src/ui/components/organisms/aboutLatestArticles/AboutLatestArticles.astro";
import AboutLatestArticles from "@components/organisms/aboutLatestArticles/AboutLatestArticles.astro";
import Breadcrumbs from "@components/molecules/breadcrumbs/Breadcrumbs.astro";
---

<BaseLayout title="" description="">
<Breadcrumbs />
<AboutIntro />
<LittleMoreOfMe />
<AboutLatestArticles />
Expand Down
8 changes: 5 additions & 3 deletions src/pages/articles/[...slug].astro
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import { Image } from "astro:assets";
import BaseLayout from "src/ui/components/templates/baseLayout/BaseLayout.astro";
import BaseLayout from "@components/templates/baseLayout/BaseLayout.astro";
import "./_article.css";
import { DEFAULT_DATE_FORMAT } from "src/consts";
import { slugify } from "src/ui/shared/utils/slugify";
import { slugify } from "@shared/utils/slugify";
import type { ImageMetadata } from "astro";
import Breadcrumbs from "@components/molecules/breadcrumbs/Breadcrumbs.astro";
export const prerender = true;
Expand All @@ -29,11 +30,11 @@ const articles = await getCollection("articles");
const { currentArticle } = Astro.props as ArticleProps;
const { featuredImage, author, title, publishDate, tags } = currentArticle.data;
const { Content } = await currentArticle.render();
const relatedArticles = articles
.filter(({ data }) => data.tags?.some((tag) => data.title !== currentArticle.data.title && tags?.includes(tag)))
.sort((a, b) => new Date(b.data.publishDate).valueOf() - new Date(a.data.publishDate).valueOf())
.splice(0, MAX_RELATED_ARTICLES);
const { Content } = await currentArticle.render();
---

<BaseLayout {...currentArticle.data}>
Expand All @@ -49,6 +50,7 @@ const relatedArticles = articles
</section>
)
}
<Breadcrumbs />
<div class="article__details">
<h1 class="article__title">
{title}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/articles/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DEFAULT_DATE_FORMAT } from "src/consts";
import "./_articles.css";
import horizontalArrow from "@assets/images/svg/left-arrow.svg";
import type { ImageMetadata } from "astro";
import Breadcrumbs from "@components/molecules/breadcrumbs/Breadcrumbs.astro";
enum ArticleType {
DEFAULT = "default",
Expand Down Expand Up @@ -42,6 +43,7 @@ const featuredArticleShareUrl = new URL(featuredArticleHref, Astro.url).href;
<!-- todo: isolate sections -->
<BaseLayout title="" description="">
<h1 class="articles__title section-title">The Blog</h1>
<Breadcrumbs />
<div class="articles__wrapper">
<section class="articles__wrapper__inner common-wrapper">
<div class="featured-article__wrapper">
Expand Down
2 changes: 2 additions & 0 deletions src/pages/contact.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import BaseLayout from "@components/templates/baseLayout/BaseLayout.astro";
import Tabs from "@components/organisms/tabs/Tabs.astro";
import ContactIntro from "@components/organisms/contactIntro/ContactIntro.astro";
import LatestArticles from "src/ui/components/organisms/latestArticles/LatestArticles.astro";
import Breadcrumbs from "@components/molecules/breadcrumbs/Breadcrumbs.astro";
---

<BaseLayout title="" description="">
<Breadcrumbs />
<ContactIntro />
<Tabs />
<LatestArticles />
Expand Down
5 changes: 3 additions & 2 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import Welcome from "@components/molecules/welcome/Welcome.astro";
import Testimonials from "@components/organisms/testimonials/Testimonials.astro";
import MyWork from "@components/organisms/myWork/MyWork.astro";
import LatestArticles from "@components/organisms/latestArticles/LatestArticles.astro";
import Breadcrumbs from "../ui/components/molecules/breadcrumbs/Breadcrumbs.astro";
// todo (current): breadcrumbs
// todo: tags page
// todo: add resume (PDF) in about?
// todo: dynamic content
// todo: theme toggle*
// todo: breadcrumbs
// todo: author collection (https://docs.astro.build/en/guides/content-collections/#defining-collection-references)
// todo: check SEO (script etc)
// todo: check SEO (jsonld, script etc)
// todo: cookies and acceptance
// todo: custom view transitions (are working) (https://www.smashingmagazine.com/2024/01/view-transitions-api-ui-animations-part2/)
// todo: theme toggle*
Expand Down
2 changes: 2 additions & 0 deletions src/pages/projects.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import BaseLayout from "@components/templates/baseLayout/BaseLayout.astro";
import { Image } from "astro:assets";
import ProjectSection from "@components/atoms/projectSection/ProjectSection.astro";
import type { ImageMetadata } from "astro";
import Breadcrumbs from "@components/molecules/breadcrumbs/Breadcrumbs.astro";
const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/**/*.{jpeg,jpg,png,gif}");
---

<BaseLayout title="" description="">
<Breadcrumbs />
<ProjectSection id="stories-of-impact">
<div class="project-description flex column-wrap justify-center align-center" slot="project-information">
<h2 class="project-title">Stories of Impact</h2>
Expand Down
39 changes: 39 additions & 0 deletions src/ui/components/molecules/breadcrumbs/Breadcrumbs.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
import { generateBreadcrumbs } from './utils/generateBreadcrumbs';
const { pathname:currentPath }= Astro.url;
const breadcrumbs = generateBreadcrumbs({ currentPath });
---

<div class="flex justify-center align-center">
{breadcrumbs?.map(({ link, label }) => {
const isLast = breadcrumbs.at(-1).link === link

return (
<>
{!isLast ? (
<a href={link}>{label}</a>
) : (
<span>{label}</span>
)}
{!isLast && " / "}
</>
);
})}
</div>

<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org/",
"@type": "BreadcrumbList",
"itemListElement": breadcrumbs.map(({ link, label }, index) => {
const isLast = breadcrumbs.at(-1).link === link

return {
"@type": "ListItem",
"position": index + 1,
"name": label,
"item": isLast ? undefined : link
};
})
})}/>
5 changes: 5 additions & 0 deletions src/ui/components/molecules/breadcrumbs/breadcrumbs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@layer breadcrumbs {
.breadcrumbs{
gap:2rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { deSlugify } from '@shared/utils/deSlugify';

interface GenerateBreadcrumbsProps{
currentPath: string,
}

export const generateBreadcrumbs = ({ currentPath }: GenerateBreadcrumbsProps) => {
const pathSegments = currentPath.split('/').filter(segment => segment.trim() !== '');
const breadcrumbs = pathSegments.map((_, index) => {
const link = `/${pathSegments.slice(0, index + 1).join('/')}`;
const label = deSlugify(pathSegments.at(index) ?? '');

return { label, link };
});

currentPath !== '/' && breadcrumbs.unshift({ label: "Home", link: "/" });

return breadcrumbs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './generateBreadcrumbs'
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { A11y, Keyboard, Navigation, Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import type { SwiperOptions } from "swiper/types";
import "./latest-articles-slider.css";
import { LatestArticlesSliderNavigation } from './components/latestArticlesSliderNavigation';
import { LatestArticlesSliderNavigation } from "./components/latestArticlesSliderNavigation";

interface LatestArticlesSLiderProps {
articles: CollectionEntry<"articles">[];
Expand Down Expand Up @@ -52,7 +52,7 @@ const parser: MarkdownIt = MarkdownIt("default", {});
export const LatestArticlesSlider = ({ articles }: LatestArticlesSLiderProps) => {
return (
<div className="latest-articles__slider common-wrapper">
<Swiper onResize={(e)=> console.log(e)} {...SLIDER_CONFIG}>
<Swiper {...SLIDER_CONFIG}>
<ul className="latest__articles__list flex row-wrap justify-space-between">
{articles.map(({ slug, data: article, ...content }) => {
const { excerpt } = createExcerpt({
Expand All @@ -69,7 +69,7 @@ export const LatestArticlesSlider = ({ articles }: LatestArticlesSLiderProps) =>
<a className="latest__article__link-card" href={href} aria-label={article.title} />
<article
className={`latest__article__item ${
variant === ArticleType.DEFAULT ? '--default-variant' : '--no-image-variant'
variant === ArticleType.DEFAULT ? "--default-variant" : "--no-image-variant"
}`}
>
{article.featuredImage && (
Expand Down
5 changes: 5 additions & 0 deletions src/ui/shared/utils/deSlugify/deSlugify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function deSlugify(slug: string): string {
return slug
.replace(/-/g, ' ')
.replace(/\b\w/g, match => match.toUpperCase());
}
1 change: 1 addition & 0 deletions src/ui/shared/utils/deSlugify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./deSlugify";

0 comments on commit 15af4d5

Please sign in to comment.