This commit is contained in:
Michael
2024-10-16 00:10:58 +13:00
commit a44225879b
59 changed files with 19369 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import Container from "@components/Container.astro";
import FormattedDate from "@components/FormattedDate.astro";
import { readingTime } from "@lib/utils";
import BackToPrev from "@components/BackToPrev.astro";
export async function getStaticPaths() {
const posts = (await getCollection("blog"))
.filter(post => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<"blog">;
const post = Astro.props;
const { Content } = await post.render();
---
<PageLayout title={post.data.title} description={post.data.description}>
<Container>
<div class="animate">
<BackToPrev href="/blog">
Back to blog
</BackToPrev>
</div>
<div class="space-y-1 my-10">
<div class="animate flex items-center gap-1.5">
<div class="font-base text-sm">
<FormattedDate date={post.data.date} />
</div>
&bull;
<div class="font-base text-sm">
{readingTime(post.body)}
</div>
</div>
<div class="animate text-2xl font-semibold text-black dark:text-white">
{post.data.title}
</div>
</div>
<article class="animate">
<Content />
</article>
</Container>
</PageLayout>
+56
View File
@@ -0,0 +1,56 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import Container from "@components/Container.astro";
import ArrowCard from "@components/ArrowCard.astro";
import { BLOG } from "@consts";
const data = (await getCollection("blog"))
.filter(post => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
type Acc = {
[year: string]: CollectionEntry<"blog">[];
}
const posts = data.reduce((acc: Acc, post) => {
const year = post.data.date.getFullYear().toString();
if (!acc[year]) {
acc[year] = [];
}
acc[year].push(post);
return acc;
}, {});
const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a));
---
<PageLayout title={BLOG.TITLE} description={BLOG.DESCRIPTION}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
Blog
</div>
<div class="space-y-4">
{years.map(year => (
<section class="animate space-y-4">
<div class="font-semibold text-black dark:text-white">
{year}
</div>
<div>
<ul class="flex flex-col gap-4">
{
posts[year].map((post) => (
<li>
<ArrowCard entry={post}/>
</li>
))
}
</ul>
</div>
</section>
))}
</div>
</div>
</Container>
</PageLayout>
+144
View File
@@ -0,0 +1,144 @@
---
import { getCollection } from "astro:content";
import Container from "@components/Container.astro";
import PageLayout from "@layouts/PageLayout.astro";
import ArrowCard from "@components/ArrowCard.astro";
import Link from "@components/Link.astro";
import { dateRange } from "@lib/utils";
import { SITE, HOME, SOCIALS } from "@consts";
const blog = (await getCollection("blog"))
.filter(post => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0,SITE.NUM_POSTS_ON_HOMEPAGE);
const projects = (await getCollection("projects"))
.filter(project => !project.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0,SITE.NUM_PROJECTS_ON_HOMEPAGE);
const allwork = (await getCollection("work"))
.sort((a, b) => new Date(b.data.dateStart).valueOf() - new Date(a.data.dateStart).valueOf())
.slice(0,SITE.NUM_WORKS_ON_HOMEPAGE);
const work = await Promise.all(
allwork.map(async (item) => {
const { Content } = await item.render();
return { ...item, Content };
})
);
---
<PageLayout title={HOME.TITLE} description={HOME.DESCRIPTION}>
<Container>
<h4 class="animate font-semibold text-black dark:text-white text-4xl font-serif">
Hi, I'm Michael <span class="text-4xl">👋🏻</span>
</h4>
<div class="space-y-16">
<section>
<article class="space-y-4">
<p class="animate">
I'm a lead software engineer at the University of Canterbury, working on <Link href="https://uconline.ac.nz" external>Tuihono UC | UC Online</Link>.
I specialize in full-stack development. While my primary expertise is in software engineering, I also have a strong interest in cloud infrastructure and DevOps. Based in Christchurch, New Zealand
<br/><br/>
Feel free to connect with me through any of the social media links at the bottom of this page!
</p>
</article>
</section>
<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
Latest posts
</h5>
<Link href="/blog">
See all posts
</Link>
</div>
<ul class="flex flex-col gap-4">
{blog.map(post => (
<li>
<ArrowCard entry={post} />
</li>
))}
</ul>
</section>
<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
Work Experience
</h5>
<Link href="/work">
See all work
</Link>
</div>
<ul class="flex flex-col space-y-4">
{work.map(entry => (
<li>
<div class="text-sm opacity-75">
{dateRange(entry.data.dateStart, entry.data.dateEnd)}
</div>
<div class="font-semibold text-black dark:text-white">
{entry.data.company}
</div>
<div class="text-sm opacity-75">
{entry.data.role}
</div>
<article>
<entry.Content />
</article>
</li>
))}
</ul>
</section>
<section class="animate space-y-6">
<div class="flex flex-wrap gap-y-2 items-center justify-between">
<h5 class="font-semibold text-black dark:text-white">
Recent projects
</h5>
<Link href="/projects">
See all projects
</Link>
</div>
<ul class="flex flex-col gap-4">
{projects.map(project => (
<li>
<ArrowCard entry={project} />
</li>
))}
</ul>
</section>
<section class="animate space-y-4">
<h5 class="font-semibold text-black dark:text-white">
Let's Connect
</h5>
<article>
<p>
If you want to get in touch with me about something or just to say hi,
reach out on social media or send me an email.
</p>
</article>
<ul class="flex flex-wrap gap-2">
{SOCIALS.map(SOCIAL => (
<li class="flex gap-x-2 text-nowrap">
<Link href={SOCIAL.HREF} external aria-label={`${SITE.NAME} on ${SOCIAL.NAME}`}>
{SOCIAL.NAME}
</Link>
{"/"}
</li>
))}
<li class="line-clamp-1">
<Link href={`mailto:${SITE.EMAIL}`} aria-label={`Email ${SITE.NAME}`}>
{SITE.EMAIL}
</Link>
</li>
</ul>
</section>
</div>
</Container>
</PageLayout>
+67
View File
@@ -0,0 +1,67 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import Container from "@components/Container.astro";
import FormattedDate from "@components/FormattedDate.astro";
import { readingTime } from "@lib/utils";
import BackToPrev from "@components/BackToPrev.astro";
import Link from "@components/Link.astro";
export async function getStaticPaths() {
const projects = (await getCollection("projects"))
.filter(post => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
return projects.map((project) => ({
params: { slug: project.slug },
props: project,
}));
}
type Props = CollectionEntry<"projects">;
const project = Astro.props;
const { Content } = await project.render();
---
<PageLayout title={project.data.title} description={project.data.description}>
<Container>
<div class="animate">
<BackToPrev href="/projects">
Back to projects
</BackToPrev>
</div>
<div class="space-y-1 my-10">
<div class="animate flex items-center gap-1.5">
<div class="font-base text-sm">
<FormattedDate date={project.data.date} />
</div>
&bull;
<div class="font-base text-sm">
{readingTime(project.body)}
</div>
</div>
<div class="animate text-2xl font-semibold text-black dark:text-white">
{project.data.title}
</div>
{(project.data.demoURL || project.data.repoURL) && (
<nav class="animate flex gap-1">
{project.data.demoURL && (
<Link href={project.data.demoURL} external>
demo
</Link>
)}
{project.data.demoURL && project.data.repoURL && (
<span>/</span>
)}
{project.data.repoURL && (
<Link href={project.data.repoURL} external>
repo
</Link>
)}
</nav>
)}
</div>
<article class="animate">
<Content />
</article>
</Container>
</PageLayout>
+30
View File
@@ -0,0 +1,30 @@
---
import { getCollection } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import Container from "@components/Container.astro";
import ArrowCard from "@components/ArrowCard.astro";
import { PROJECTS } from "@consts";
const projects = (await getCollection("projects"))
.filter(project => !project.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<PageLayout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
Projects
</div>
<ul class="animate flex flex-col gap-4">
{
projects.map((project) => (
<li>
<ArrowCard entry={project}/>
</li>
))
}
</ul>
</div>
</Container>
</PageLayout>
+16
View File
@@ -0,0 +1,16 @@
import type { APIRoute } from "astro";
const robotsTxt = `
User-agent: *
Allow: /
Sitemap: ${new URL("sitemap-index.xml", import.meta.env.SITE).href}
`.trim();
export const GET: APIRoute = () => {
return new Response(robotsTxt, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
};
+30
View File
@@ -0,0 +1,30 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import { HOME } from "@consts";
type Context = {
site: string
}
export async function GET(context: Context) {
const blog = (await getCollection("blog"))
.filter(post => !post.data.draft);
const projects = (await getCollection("projects"))
.filter(project => !project.data.draft);
const items = [...blog, ...projects]
.sort((a, b) => new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf());
return rss({
title: HOME.TITLE,
description: HOME.DESCRIPTION,
site: context.site,
items: items.map((item) => ({
title: item.data.title,
description: item.data.description,
pubDate: item.data.date,
link: `/${item.collection}/${item.slug}/`,
})),
});
}
+51
View File
@@ -0,0 +1,51 @@
---
import { getCollection } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import Container from "@components/Container.astro";
import { dateRange } from "@lib/utils";
import { WORK } from "@consts";
const collection = (await getCollection("work"))
.sort((a, b) => new Date(b.data.dateStart).valueOf() - new Date(a.data.dateStart).valueOf());
const work = await Promise.all(
collection.map(async (item) => {
const { Content } = await item.render();
return { ...item, Content };
})
);
---
<PageLayout title={WORK.TITLE} description={WORK.DESCRIPTION}>
<Container>
<div class="space-y-10">
<div class="animate font-semibold text-black dark:text-white">
Work
</div>
<ul class="flex flex-col space-y-4">
{
work.map(entry => (
<li class="animate">
<div class="text-sm opacity-75">
{dateRange(entry.data.dateStart, entry.data.dateEnd)}
</div>
<div class="font-semibold text-black dark:text-white">
{entry.data.company}
</div>
<div class="text-sm opacity-75">
{entry.data.role}
</div>
<article>
<entry.Content />
</article>
</li>
))
}
</ul>
<!--
<ul class="animate flex flex-col gap-4">
</ul> -->
</div>
</Container>
</PageLayout>