310 lines
9.7 KiB
TypeScript
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>Lê 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>
|
|
</>
|
|
)
|
|
}
|