personal-website/app/page.tsx
Filipe Medeiros 5ff4edc634
feat: lots of stuff sorry
Signed-off-by: Filipe Medeiros <hello@filipesm.eu>
2023-12-03 12:25:54 +01:00

310 lines
9.7 KiB
TypeScript

import Head from 'next/head'
import Image, { ImageProps } from 'next/image'
import { type ElementType } from 'react'
import Button from '@/components/client/Button'
import Card, {
CardCta,
CardDescription,
CardEyebrow,
CardTitle,
} from '@/components/server/Card'
import Container from '@/components/server/Container'
import HybridLink, {
type Props as HybridLinkProps,
} from '@/components/server/HybridLink'
import {
CodebergIcon,
GitHubIcon,
type IconProps,
MastodonLogo,
RSSIcon,
} from '@/components/server/icons'
import EmmaLogo from '@/images/logos/emma.jpeg'
import FarfetchLogo from '@/images/logos/farfetch.png'
import ThreeSigmaLogo from '@/images/logos/threeSigma.png'
import formatDate from '@/lib/formatDate'
import { getFirstBlogPosts } from '@/lib/notion/content/blogPosts'
import richTextAsPlainText from '@/lib/notion/utils/richTextToPlainText'
function BriefcaseIcon(props: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
<path
d="M2.75 9.75a3 3 0 0 1 3-3h12.5a3 3 0 0 1 3 3v8.5a3 3 0 0 1-3 3H5.75a3 3 0 0 1-3-3v-8.5Z"
className="stroke-current"
/>
<path
d="M3 14.25h6.249c.484 0 .952-.002 1.316.319l.777.682a.996.996 0 0 0 1.316 0l.777-.682c.364-.32.832-.319 1.316-.319H21M8.75 6.5V4.75a2 2 0 0 1 2-2h2.5a2 2 0 0 1 2 2V6.5"
className="stroke-current"
/>
</svg>
)
}
function ArrowDownIcon(props: IconProps) {
return (
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true" {...props}>
<path
d="M4.75 8.75 8 12.25m0 0 3.25-3.5M8 12.25v-8.5"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
function BlogPost({
slug,
title,
description,
publishDate,
}: {
slug: string
title: string
description: string
publishDate: string
}) {
return (
<Card as="article">
<CardTitle href={`/blog/${slug}`}>{title}</CardTitle>
<CardEyebrow as="time" dateTime={publishDate} decorate>
{formatDate(publishDate)}
</CardEyebrow>
<CardDescription>{description}</CardDescription>
<CardCta> o artigo</CardCta>
</Card>
)
}
function SocialLink({
icon: Icon,
...props
}: HybridLinkProps & { icon: ElementType }) {
return (
<HybridLink className="group -m-1 p-1" {...props}>
<Icon className="h-6 w-6 fill-zinc-500 transition group-hover:fill-zinc-600 dark:fill-zinc-400 dark:group-hover:fill-zinc-300" />
</HybridLink>
)
}
function StayUpToDate() {
return (
<div className="rounded-2xl border border-zinc-100 p-6 dark:border-zinc-700/40">
<h2 className="flex text-sm font-semibold text-zinc-900 dark:text-zinc-100">
<RSSIcon className="h-6 w-6 flex-none text-teal-500" />
<span className="ml-3">Mantém-te atualizado</span>
</h2>
<p className="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
Subscreve o <i>feed</i> de RSS{' '}
<HybridLink
className="underline transition hover:text-teal-500"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/rss/feed.xml`}
>
aqui
</HybridLink>
!
</p>
</div>
)
}
function Resume() {
const resume: {
company: string
title: string
logo: ImageProps['src']
start:
| {
label: string
dateTime: string
}
| string
end:
| {
label: string
dateTime: string
}
| string
}[] = [
{
company: 'Farfetch',
title: 'Junior Frontend Software Eng.',
start: '2020',
end: '2021',
logo: FarfetchLogo,
},
{
company: 'Emma - The Sleep Company',
title: 'Frontend Software Eng.',
start: '2022',
end: '2022',
logo: EmmaLogo,
},
{
company: 'Three Sigma',
title: 'Frontend Software Eng.',
start: '2022',
end: 'Present',
logo: ThreeSigmaLogo,
},
]
return (
<div className="rounded-2xl border border-zinc-100 p-6 dark:border-zinc-700/40">
<h2 className="flex text-sm font-semibold text-zinc-900 dark:text-zinc-100">
<BriefcaseIcon className="h-6 w-6 flex-none text-teal-500" />
<span className="ml-3">Trabalho</span>
</h2>
<ol className="mt-6 space-y-4">
{resume.map((role, roleIndex) => (
<li key={roleIndex} className="flex gap-4">
<Image src={role.logo} alt="" className="h-10 w-10 rounded" />
<dl className="flex flex-auto flex-wrap gap-x-2">
<dt className="sr-only">Company</dt>
<dd className="w-full flex-none text-sm font-medium text-zinc-900 dark:text-zinc-100">
{role.company}
</dd>
<dt className="sr-only">Role</dt>
<dd className="text-xs text-zinc-500 dark:text-zinc-400">
{role.title}
</dd>
<dt className="sr-only">Date</dt>
<dd
className="ml-auto text-xs text-zinc-400 dark:text-zinc-500"
aria-label={`${
typeof role.start === 'string' ? role.start : role.start.label
} until ${
typeof role.end === 'string' ? role.end : role.end.label
}`}
>
<time
dateTime={
typeof role.start === 'string'
? role.start
: role.start.dateTime
}
>
{typeof role.start === 'string'
? role.start
: role.start.label}
</time>{' '}
<span aria-hidden="true"></span>{' '}
<time
dateTime={
typeof role.end === 'string' ? role.end : role.end.dateTime
}
>
{typeof role.end === 'string' ? role.end : role.end.label}
</time>
</dd>
</dl>
</li>
))}
</ol>
<Button href="/CV.pdf" variant="colored" className="group mt-6 w-full">
O meu CV
<ArrowDownIcon className="h-4 w-4 stroke-current" />
</Button>
</div>
)
}
export default async function Home() {
const { results: blogPosts } = await getFirstBlogPosts()
return (
<>
<Head>
<title>
Spencer Sharp - Software designer, founder, and amateur astronaut
</title>
<meta
name="description"
content="I'm Spencer, a software designer and entrepreneur based in New York City. I'm the founder and CEO of Planetaria, where we develop technologies that empower regular people to explore space on their own terms."
/>
</Head>
<Container className="mt-9">
<div className="max-w-2xl">
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
Olá! Sou o Filipe :)
</h1>
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
Sou um engenheiro de UX (não me perguntem o que é que nem eu sei
bem).
</p>
<p className="text-base text-zinc-600 dark:text-zinc-400">
Gosto de programar interfaces e aplicações web, mas também de
política, de ler sobre economia e de animais. Não gosto de cozinhar
nem de vento frio. 🥶
</p>
<p className="text-base text-zinc-600 dark:text-zinc-400">
Estou a tentar trazer à superfície o potencial da humanidade. Sou a
favor de standards, software de código aberto, licenças{' '}
<HybridLink
href="https://pt.wikipedia.org/wiki/Copyleft"
className="underline transition-colors hover:text-teal-500"
>
<i>copyleft</i>
</HybridLink>{' '}
e de cooperativas em vez de monopólios.
</p>
<p className="text-base text-zinc-600 dark:text-zinc-400">
Moro em Oeiras, Lisboa, Portugal.
</p>
<div className="mt-6 flex gap-6">
<SocialLink
href="https://mastodon.green/@filipesm"
aria-label="Segue-me no Fediverse"
icon={MastodonLogo}
/>
<SocialLink
href="https://codeberg.org/filipesm"
aria-label="Segue-me no Codeberg"
icon={CodebergIcon}
/>
<SocialLink
href="https://github.com/filipesmedeiros"
aria-label="Segue-me no GitHub"
icon={GitHubIcon}
/>
</div>
</div>
</Container>
<Container className="mt-24 md:mt-28">
<div className="mx-auto grid max-w-xl grid-cols-1 gap-y-20 lg:max-w-none lg:grid-cols-2">
<div className="flex flex-col gap-16">
{blogPosts.map((blogPost) => (
<BlogPost
key={richTextAsPlainText(blogPost.properties.Slug.rich_text)}
title={richTextAsPlainText(blogPost.properties.Name.title)}
description={richTextAsPlainText(
blogPost.properties.Description.rich_text,
)}
slug={richTextAsPlainText(blogPost.properties.Slug.rich_text)}
publishDate={richTextAsPlainText(
blogPost.properties.PublishDate.date?.start ??
blogPost.created_time,
)}
/>
))}
</div>
<div className="space-y-10 lg:pl-16 xl:pl-24">
<StayUpToDate />
<Resume />
</div>
</div>
</Container>
</>
)
}