not fresh lol

Signed-off-by: Filipe Medeiros <hello@filipesm.eu>
This commit is contained in:
Filipe Medeiros 2023-04-09 02:30:10 +01:00
parent 9aa01bc8c1
commit b973949620
Signed by: filipe
GPG key ID: 9533BD5467CC1E78
35 changed files with 4362 additions and 0 deletions

21
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

13
frontend/.prettierrc Normal file
View file

@ -0,0 +1,13 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
"pluginSearchDirs": false,
"importOrder": ["<THIRD_PARTY_MODULES>", "^@/", "^(./)", "(.*).(css|.min.js)", "(.*).(jpeg|jpg|png|webp|gif)"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}

4
frontend/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
frontend/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

55
frontend/README.md Normal file
View file

@ -0,0 +1,55 @@
# Astro Starter Kit: Basics
```
npm create astro@latest -- --template basics
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

8
frontend/astro.config.js Normal file
View file

@ -0,0 +1,8 @@
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
integrations: [react(), tailwind()],
});

View file

@ -0,0 +1,11 @@
import { createClient } from '@sanity/client';
const client = createClient({
projectId: 'tzamgyrm',
dataset: 'production',
apiVersion: '2023-02-08',
useCdn: false,
token: import.meta.env.PUBLIC_SANITY_TOKEN,
});
export default client;

View file

@ -0,0 +1,23 @@
import groq from 'groq';
export default function getMostRecentRevisions(filter: string) {
return groq`
{
"drafts": select(
!$live => *[
${filter} &&
_id in path("drafts.**")
],
$live => []
),
"published": *[
${filter} &&
!(_id in path("drafts.**"))
],
}
{
"current": published[
!("drafts." + @._id in ^.drafts[]._id)
] + drafts
}.current[]`;
}

View file

@ -0,0 +1,22 @@
import getMostRecentRevisions from '../getMostRecentRevisions';
import groq from 'groq';
export const forBlogPostPage = groq`
${getMostRecentRevisions(groq`_type == "blogPost"`)}
{
"slug": slug.current,
title,
content,
headerImage,
publishDate
}`;
export const forBlogPage = groq`
${getMostRecentRevisions(groq`_type == "blogPost"`)}
{
title,
summary,
publishDate,
"slug": slug.current
}
`;

View file

@ -0,0 +1,12 @@
import getMostRecentRevisions from '../getMostRecentRevisions';
import groq from 'groq';
export const forLibraryPage = groq`
${getMostRecentRevisions(groq`_type == "libraryItem"`)}
{
title,
description,
checkedOut,
link
}
`;

32
frontend/package.json Normal file
View file

@ -0,0 +1,32 @@
{
"name": "personal-website",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^2.1.1",
"@astrojs/tailwind": "^3.1.1",
"@portabletext/react": "^2.0.2",
"@sanity/client": "^5.4.2",
"astro": "^2.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@portabletext/types": "^2.0.2",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"autoprefixer": "^10.4.14",
"groq": "^3.8.3",
"postcss": "^8.4.21",
"prettier-plugin-tailwindcss": "^0.2.7",
"tailwindcss": "^3.3.1"
}
}

3700
frontend/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
frontend/public/CV.pdf Normal file

Binary file not shown.

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
frontend/public/me.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -0,0 +1,19 @@
{
"name": "Filipe Medeiros - website pessoal",
"short_name": "Filipe",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#14B8A6",
"background_color": "#14B8A6",
"display": "standalone"
}

View file

@ -0,0 +1,29 @@
import {
PortableText,
type PortableTextBlockComponent,
} from '@portabletext/react';
import type { PortableTextBlock } from '@portabletext/types';
export interface Props {
content: PortableTextBlock[];
}
export default function BlogPostContent({ content }: Props) {
return (
<div className="text-justify">
<PortableText
components={{
block: { h1: () => <h1>loli</h1> },
marks: {
code: ({ children }) => (
<code className="bg-stone-800 p-0.5 px-1 text-white">
{children}
</code>
),
},
}}
value={content}
/>
</div>
);
}

View file

@ -0,0 +1,15 @@
---
import type { HTMLAttributes } from 'astro/types';
export type Props = HTMLAttributes<'a'>;
---
<a
class:list={[
'bg-primary-800 text-white shadow-[5px_5px] shadow-primary-600 hover:shadow-primary-600 hover:shadow-[3px_3px] active:shadow-none',
Astro.props.class,
]}
{...Astro.props}
>
<slot />
</a>

View file

@ -0,0 +1,32 @@
---
export interface Props {
link: string;
}
const random = Math.round(Math.random() * 10) % 4;
const { link } = Astro.props;
---
<li
class:list={[
'border-primary-800 border-2 w-96 max-w-full mb-3 group/card',
{
'hover:shadow-[-3px_-3px] over:shadow-primary-800 ctive:shadow-[-5px_-5px] ctive:shadow-primary-800':
random === 0,
'hover:shadow-[-3px_3px] hover:shadow-primary-800 active:shadow-[-5px_5px] active:shadow-primary-800':
random === 1,
'hover:shadow-[3px_-3px] hover:shadow-primary-800 active:shadow-[5px_-5px] active:shadow-primary-800':
random === 2,
'hover:shadow-[3px_3px] hover:shadow-primary-800 active:shadow-[5px_5px] active:shadow-primary-800':
random === 3,
},
]}
>
<a href={link} class="px-4 py-3 block">
<slot />
<div class="flex justify-end">
<span class="pr-3 text-xl group-hover/card:pr-0">→</span>
</div>
</a>
</li>

View file

@ -0,0 +1,13 @@
---
import type { HTMLAttributes } from 'astro/types';
export type Props = HTMLAttributes<'a'>;
---
<a
class:list={[
'text-primary-800 underline hover:text-primary-700',
Astro.props.class,
]}
{...Astro.props}><slot /></a
>

View file

@ -0,0 +1,12 @@
---
import type { HTMLAttributes } from 'astro/types';
export type Props = HTMLAttributes<'h1'>;
---
<h1
class:list={['text-primary-800 font-bold text-4xl', Astro.props.class]}
{...Astro.props}
>
<slot />
</h1>

1
frontend/src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

View file

@ -0,0 +1,39 @@
---
import InlineLink from '../components/InlineLink.astro';
export interface Props {
title: string;
}
const { title } = Astro.props;
const { footer } = Astro.slots;
const hasFooter = !!footer;
---
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/favicon" href="/favicon.ico" />
<title>{title}</title>
</head>
<body class="bg-orange-50 font-serif">
<main class:list={['px-6 pt-4 md:px-20 md:pt-6', hasFooter && 'mb-32']}>
<slot />
</main>
{
hasFooter && (
<footer class="fixed bottom-0 p-4 pb-0 flex flex-col w-full gap-2">
<slot name="footer" />
<div class="flex gap-3 justify-end bg-orange-50 pb-2">
<InlineLink href="https://mastodon.green/@filipesm">
Mastodon
</InlineLink>
<InlineLink href="https://codeberg/filipesm">Codeberg</InlineLink>
<InlineLink href="https://bandcamp/filipesm">Bandcamp</InlineLink>
</div>
</footer>
)
}
</body>
</html>

View file

@ -0,0 +1,58 @@
---
import client from '../../../lib/cms/client';
import { forLibraryPage } from '../../../lib/cms/queries/libraryItems';
import ButtonLink from '../../components/ButtonLink.astro';
import CardLink from '../../components/CardLink.astro';
import PageTitle from '../../components/PageTitle.astro';
import Layout from '../../layouts/Layout.astro';
const libraryItems: {
description: string;
checkedOut: boolean;
link: string;
title: string;
}[] = await client.fetch(forLibraryPage, {
live: !import.meta.env.DEV,
});
---
<Layout title="Biblioteca - Filipe Medeiros">
<header class="mb-10">
<PageTitle class="mb-3">Biblioteca</PageTitle>
<h2>
Livros, artigos, <span class="italic">etc.</span> que já li e que recomendo
ou que quero ler num futuro próximo. Se um item tiver uma marca "<span
class="text-primary-700">✓</span
>", siginifca que eu já li, vi, etc.
</h2>
</header>
<ol>
{
libraryItems.map(({ title, description, link, checkedOut }) => (
<CardLink link={link}>
<span class="text-lg md:text-xl font-medium break-words text-primary-700 block mb-3">
{title}
</span>
<p class="mb-5">{description}</p>
{checkedOut && <span class="text-primary-700 text-3xl">✓</span>}
</CardLink>
))
}
</ol>
<Fragment slot="footer">
<nav class="flex gap-4">
<ButtonLink
href="/blog"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Blog
</ButtonLink>
<ButtonLink
href="/"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Início
</ButtonLink>
</nav>
</Fragment>
</Layout>

View file

@ -0,0 +1,53 @@
---
import type { PortableTextBlock } from '@portabletext/types';
import client from '../../../../lib/cms/client';
import { forBlogPostPage } from '../../../../lib/cms/queries/blogPosts';
import BlogPostContent from '../../../components/BlogPostContent';
import ButtonLink from '../../../components/ButtonLink.astro';
import PageTitle from '../../../components/PageTitle.astro';
import Layout from '../../../layouts/Layout.astro';
export async function getStaticPaths() {
const posts: {
slug: string;
title: string;
content: PortableTextBlock[];
publishDate: string;
}[] = await client.fetch(forBlogPostPage, { live: !import.meta.env.DEV });
return posts.map(({ slug, title, content, publishDate }) => {
return {
params: { slug },
props: { title, content, publishDate },
};
});
}
const { title, content, publishDate } = Astro.props;
---
<Layout title={`${title} - Filipe Medeiros`}>
<article class="max-w-prose m-auto">
<header class="mb-5">
<PageTitle class="mb-2 break-words">{title}</PageTitle>
<time datetime={publishDate} class="text-xl">{publishDate}</time>
</header>
<BlogPostContent content={content} />
</article>
<Fragment slot="footer">
<nav class="flex gap-4">
<ButtonLink
href="/blog"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Blog
</ButtonLink>
<ButtonLink
href="/biblioteca"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Biblioteca
</ButtonLink>
</nav>
</Fragment>
</Layout>

View file

@ -0,0 +1,55 @@
---
import client from '../../../lib/cms/client';
import { forBlogPage } from '../../../lib/cms/queries/blogPosts';
import ButtonLink from '../../components/ButtonLink.astro';
import CardLink from '../../components/CardLink.astro';
import PageTitle from '../../components/PageTitle.astro';
import Layout from '../../layouts/Layout.astro';
const posts: {
title: string;
publishDate: string;
summary: string;
slug: string;
}[] = await client.fetch(forBlogPage, { live: !import.meta.env.DEV });
---
<Layout title="Blog - Filipe Medeiros">
<header class="mb-10">
<PageTitle class="mb-3">Blog</PageTitle>
<h2>Uma coleção de artigos que vou escrevendo. Sobretudo por diversão.</h2>
</header>
<ol>
{
posts.map(({ title, slug, publishDate, summary }) => (
<CardLink link={`/blog/${slug}`}>
<article class="flex flex-col">
<span class="text-lg md:text-xl font-medium break-words text-primary-700">
{title}
</span>
<time datetime={publishDate} class="text-sm text-primary-800">
{publishDate}
</time>
<span>{summary}</span>
</article>
</CardLink>
))
}
</ol>
<Fragment slot="footer">
<nav class="flex gap-4">
<ButtonLink
href="/"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Início
</ButtonLink>
<ButtonLink
href="/biblioteca"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Biblioteca
</ButtonLink>
</nav>
</Fragment>
</Layout>

View file

@ -0,0 +1,88 @@
---
import ButtonLink from '../components/ButtonLink.astro';
import InlineLink from '../components/InlineLink.astro';
import PageTitle from '../components/PageTitle.astro';
import Layout from '../layouts/Layout.astro';
---
<Layout title="Página inicial - Filipe Medeiros">
<PageTitle class="mb-10">
Filipe<br />Medeiros
</PageTitle>
<div class="max-w-[60ch]">
<p class="mb-3">
Sou um engenheiro de <InlineLink
href="https://www.usability.gov/what-and-why/user-experience.html"
class="italic"
>
UX
</InlineLink> (não me perguntem o que é, que nem eu sei bem).
</p>
<p class="mb-3">
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 (ainda) nem de
vento frio.
</p>
<p class="mb-3">
Estou a tentar trazer à superfície o potencial da humanidade. Sou a favor
de <InlineLink href="https://www.w3.org/standards/faq#std" class="italic">
standards
</InlineLink>, software de código aberto (<InlineLink
href="https://codeberg.org/filipesm/personal-website"
>
como é este site!
</InlineLink>), <InlineLink href="https://pt.wikipedia.org/wiki/Copyleft">
licenças <span class="italic">copyleft</span>
</InlineLink>
e de
<InlineLink
href="https://www.ica.coop/en/cooperatives/what-is-a-cooperative"
>
cooperativas
</InlineLink> em vez de monopólios.
</p>
<p class="mb-3">
Sou apaixonado pela <InlineLink
href="https://doughnuteconomics.org/about-doughnut-economics"
class="italic"
>
Economia do Donut
</InlineLink> e por economia social e ambiental. Gostava de, um dia, ser economista
em vez de programador. Quem sabe, para a União Europeia.
</p>
<p class="mb-3">
Moro em <InlineLink href="https://www.oeiras.pt">
Oeiras, Lisboa, Portugal
</InlineLink>.
</p>
<p class="mb-3">
Se quiseres ver, tens <InlineLink href="/me.jpeg">aqui</InlineLink> uma foto
minha!
</p>
<p>
Este site tenta ser o mais sustentável possível: poupamos energia no teu
dispositivo e em servidores. Teve muita inspiração da{' '}
<InlineLink href="https://solar.lowtechmagazine.com">
versão solar da Low Tech Magazine
</InlineLink>.
</p>
</div>
<Fragment slot="footer">
<nav class="flex gap-4">
<ButtonLink
href="/blog"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Blog
</ButtonLink>
<ButtonLink
href="/biblioteca"
class="text-3xl flex-1 py-2 flex justify-center px-2"
>
Biblioteca
</ButtonLink>
</nav>
</Fragment>
</Layout>

View file

@ -0,0 +1,23 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
colors: {
primary: {
50: '#e5fff7',
100: '#b3ffe8',
200: '#80ffd8',
300: '#4dffc9',
400: '#1affb9',
500: '#00e6a0',
600: '#00b37c',
700: '#008059',
800: '#004d35',
900: '#001a12',
},
},
},
},
plugins: [],
}

7
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}