diff --git a/src/actions/index.ts b/src/actions/index.ts index 94bc4d4f..a4ee85d6 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -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; - -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 }); - } - }, - }), -}; \ No newline at end of file +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; + +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 }); + } + }, + }), +}; diff --git a/src/pages/about.astro b/src/pages/about.astro index 70b9ffe3..7cbff900 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -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"; --- + diff --git a/src/pages/articles/[...slug].astro b/src/pages/articles/[...slug].astro index 33028b3a..4c92d56d 100644 --- a/src/pages/articles/[...slug].astro +++ b/src/pages/articles/[...slug].astro @@ -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; @@ -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(); --- @@ -49,6 +50,7 @@ const relatedArticles = articles ) } +

{title} diff --git a/src/pages/articles/index.astro b/src/pages/articles/index.astro index bf28809d..3d24dfe9 100644 --- a/src/pages/articles/index.astro +++ b/src/pages/articles/index.astro @@ -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", @@ -42,6 +43,7 @@ const featuredArticleShareUrl = new URL(featuredArticleHref, Astro.url).href;

The Blog

+
diff --git a/src/pages/contact.astro b/src/pages/contact.astro index d546869a..73842c0f 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -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"; --- + diff --git a/src/pages/index.astro b/src/pages/index.astro index dc0e17fe..0d0a5c39 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -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* diff --git a/src/pages/projects.astro b/src/pages/projects.astro index bd83816d..b406aa5f 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -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}"); --- +

Stories of Impact

diff --git a/src/ui/components/molecules/breadcrumbs/Breadcrumbs.astro b/src/ui/components/molecules/breadcrumbs/Breadcrumbs.astro new file mode 100644 index 00000000..cc0bb3bb --- /dev/null +++ b/src/ui/components/molecules/breadcrumbs/Breadcrumbs.astro @@ -0,0 +1,39 @@ +--- +import { generateBreadcrumbs } from './utils/generateBreadcrumbs'; + +const { pathname:currentPath }= Astro.url; + +const breadcrumbs = generateBreadcrumbs({ currentPath }); +--- + +
+ {breadcrumbs?.map(({ link, label }) => { + const isLast = breadcrumbs.at(-1).link === link + + return ( + <> + {!isLast ? ( + {label} + ) : ( + {label} + )} + {!isLast && " / "} + + ); + })} +
+ +