feat: integrate with contentfull

main
Jalil Arfaoui 2024-01-02 00:34:17 +01:00
parent 3c94fcf793
commit f067f77918
15 changed files with 6866 additions and 5631 deletions

6
.idea/encodings.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/pages/test-evenement.astro" charset="UTF-8" />
</component>
</project>

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
yarnPath: .yarn/releases/yarn-3.6.3.cjs

View File

@ -1,4 +1,10 @@
import { defineConfig } from 'astro/config';
import node from "@astrojs/node";
// https://astro.build/config
export default defineConfig({});
export default defineConfig({
output: "hybrid",
adapter: node({
mode: "standalone"
})
});

6479
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,12 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.2"
"@astrojs/node": "^7.0.3",
"@contentful/rich-text-html-renderer": "^16.3.0",
"astro": "^4.0.8",
"contentful": "^10.6.15"
},
"packageManager": "yarn@3.6.3"
"devDependencies": {
"@types/node": "^20.10.6"
}
}

View File

@ -7,10 +7,12 @@ export interface Props {
}
const { href, target, title, span} = Astro.props;
const Wrapper = href ? 'a' : 'div'
---
<li class:list={["link-card",{ span }]} >
<a href={href} target={target}>
<Wrapper class="content" href={href} target={target}>
<h2>
{title}
<span>&rarr;</span>
@ -18,7 +20,7 @@ const { href, target, title, span} = Astro.props;
<p>
<slot />
</p>
</a>
</Wrapper>
</li>
<style>
.link-card {
@ -26,18 +28,16 @@ const { href, target, title, span} = Astro.props;
display: flex;
padding: 0.15rem;
background-color: white;
background-image: var(--accent-gradient);
background-size: 400%;
border-radius: 0.5rem;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
box-shadow: 0.2em 0.2em 1em black;
max-width: 80ch;
}
.link-card.span {
grid-column: 1/-1;
}
.link-card > a {
.link-card > .content {
width: 100%;
text-decoration: none;
line-height: 1.4;

View File

@ -0,0 +1,97 @@
---
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
const { evenement } = Astro.props;
---
<section class="evenement">
<a href=`/evenements/${evenement.slug}`>
<img src={evenement.affiche.fields.file.url} alt=`Affiche de ${evenement.nom}` />
</a>
<div class="infos">
<div class="nom">
<a href=`/evenements/${evenement.slug}`>
{evenement.nom}
</a>
</div>
<div class="date">
le {new Date(evenement.date).toLocaleDateString()}
</div>
<div class="lieu">
à {evenement.lieu}
</div>
<div class="description">
<Fragment set:html={documentToHtmlString(evenement.description)} />
</div>
<a class="suite" href=`/evenements/${evenement.slug}`>
Suite…
</a>
</div>
</section>
<style>
.evenement {
display: flex;
flex-direction: row;
column-gap: 1em;
color:rgb(68,68,68);
font-size: 1.25rem;
line-height: 1.4;
.nom {
font-weight: 700;
font-size:20px;
a {
text-decoration: none;
color: rgb(17,17,17);
}
}
& > a {
width: 30%;
flex-grow: 0;
}
img {
width: 100%;
}
.infos {
flex-grow: 0;
width: 70%;
position: relative;
.description {
font-size: 0.8em;
max-height: 10em;
overflow: hidden;
}
.suite {
font-size: 0.7em;
position: absolute;
bottom: 1em;
right: 1em;
}
}
}
@media (max-width: 48em) {
.evenement {
flex-direction: column;
& > a {
width: 100%;
}
.infos {
.description, .date, .lieu, .suite {
display: none
}
}
}
}
</style>

6
src/env.d.ts vendored
View File

@ -1 +1,7 @@
/// <reference types="astro/client" />
interface ImportMetaEnv {
readonly CONTENTFUL_SPACE_ID: string;
readonly CONTENTFUL_DELIVERY_TOKEN: string;
readonly CONTENTFUL_PREVIEW_TOKEN: string;
readonly WEBHOOK_TOKEN: string;
}

View File

@ -7,24 +7,32 @@ const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="./favicon.jpg" />
<link rel="icon" type="image/svg+xml" href="/favicon.jpg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body style="
background: url('/logo_les-particules_orange.jpg') top no-repeat;
height:fit-content; min-height: 100% ">
<main>
<h1>
<a href="/">
<img alt="Logo" src="/logo_les-particules_noir.jpg" style="width:80px; vertical-align: middle" />
Les particules
</a>
</h1>
<slot />
</main>
</body>
</html>
<style is:global>
:root {
--accent: 124, 58, 237;
--accent-gradient: linear-gradient(45deg, rgb(var(--accent)), #da62c4 30%, white 60%);
--accent: orange;
--accent-gradient: linear-gradient(45deg, var(--accent), red 30%, white 60%);
}
html {
font-family: system-ui, sans-serif;
@ -36,6 +44,17 @@ const { title } = Astro.props;
margin:0;
}
h1 a {
text-decoration: none;
color:black;
}
main {
margin: auto;
padding: 1.5rem;
max-width: 80ch;
}
code {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;

40
src/lib/contentful.ts Normal file
View File

@ -0,0 +1,40 @@
import contentful, {type Entry, type EntryFieldTypes} from "contentful";
export const contentfulClient = contentful.createClient({
space: import.meta.env.CONTENTFUL_SPACE_ID,
accessToken: import.meta.env.CONTENTFUL_DELIVERY_TOKEN,
host: "cdn.contentful.com",
});
export interface ContentFulEvenement {
contentTypeId: "evenement",
fields: {
nom: EntryFieldTypes.Text
slug: EntryFieldTypes.Text,
description: EntryFieldTypes.RichText,
date: EntryFieldTypes.Date,
lieu: EntryFieldTypes.Location,
affiche: EntryFieldTypes.AssetLink,
}
}
export const evenementFromContentfull = ({ fields: { nom, slug, description, date, affiche, lieu } }: Entry<ContentFulEvenement>) => ({
nom, slug, description, date, affiche, lieu
})
export const sortByDate = (evenements: Entry<ContentFulEvenement>[]): Entry<ContentFulEvenement>[] => {
return evenements.sort((a, b) => {
if (!a.fields.date) return 1;
if (!b.fields.date) return -1;
return new Date(a.fields.date as string).getTime() - new Date(b.fields.date as string).getTime();
});
};
export const fetchEvenements = async () => {
const entries = await contentfulClient.getEntries<ContentFulEvenement>({
content_type: "evenement",
});
return sortByDate(entries.items).map(evenementFromContentfull)
}

60
src/pages/api/webhook.ts Normal file
View File

@ -0,0 +1,60 @@
import { exec } from 'child_process';
let isBuildInProgress = false;
export async function POST({ request }: { request: Request }): Promise<Response> {
const data = await request.json();
if (!data.token || data.token !== import.meta.env.WEBHOOK_TOKEN) {
return new Response(
JSON.stringify({ status: "unauthorized" }),
{ status: 401, headers: { "Content-Type": "application/json" } }
)
}
try {
if (isBuildInProgress) {
return new Response(
JSON.stringify({status: 'error', message: 'Build already in progress'}),
{
status: 409,
headers: {
'Content-Type': 'application/json',
},
}
);
}
isBuildInProgress = true;
exec('npm run build', (error, stdout, stderr) => {
if (error) {
console.error(`Error executing build: ${error}`);
}
if (stdout) {
console.log(`Build stdout: ${stdout}`);
}
if (stderr) {
console.error(`Build stderr: ${stderr}`);
}
isBuildInProgress = false;
});
return new Response(JSON.stringify({status: 'success'}), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error("Error in webhook:", error);
// Return a error response
return new Response(JSON.stringify({status: 'error', message: error?.toString()}), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
}

View File

@ -0,0 +1,79 @@
---
import { Document } from '@contentful/rich-text-types';
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import { fetchEvenements} from "../../lib/contentful";
import Layout from "../../layouts/Layout.astro";
import Card from "../../components/Card.astro";
export async function getStaticPaths() {
const evenement = await fetchEvenements()
return evenement.map((evenement) => ({
params: {slug: evenement.slug},
props: {
nom: evenement.nom,
description: evenement.description ? documentToHtmlString(evenement.description as Document) : "",
date: evenement.date ? new Date(evenement.date as string).toLocaleDateString() : "",
lieu: evenement.lieu,
affiche: evenement.affiche
},
}));
}
const { nom, description, date, lieu, affiche } = Astro.props;
---
<Layout>
<h1>{nom}</h1>
<div class="content">
<img alt=`Affiche de ${nom}` src={affiche.fields.file.url} />
<Card title={date}>
<div>
à <a target="_blank" href="https://cartessurtable.wixsite.com/cartes-sur-table">{lieu}</a>
</div>
<article set:html={description} />
</Card>
</div>
</Layout>
<style is:global>
main {
margin: auto;
padding: 1.5rem;
max-width: none;
}
main:after {
display: block;
content: '';
clear: both;
}
</style>
<style>
img {
float:left;
width: 30%;
margin: 1em;
}
article {
font-size: 1.2em;
}
@media (max-width: 48em) {
.content {
display: flex;
flex-direction: column-reverse;
img {
float:none;
width: 100%;
border: black solid 0.1em;
border-radius: 1em;
margin: 1em auto;
box-shadow: 0.2em 0.2em 1em black;
}
}
}
</style>

View File

@ -1,30 +1,30 @@
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import { fetchEvenements } from "../lib/contentful";
import Evenement from "../components/Evenement.astro";
const evenements = await fetchEvenements()
---
<Layout title="Les Particules">
<main>
<h1>
<img alt="Logo" src="/logo_les-particules_noir.jpg" style="width:80px; vertical-align: middle" />
Les particules
</h1>
<p class="instructions">
La troupe d'improvisation théâtrale chatouilleuse, curieuse, créative et autogérée<br/>
📌 Albi, Occitanie
</p>
<div class="dates">
<span class="title">Retrouvez-nous prochainement <span>&rarr;</span></span>
<ul>
<li>28 octobre 2023 à 19h30 au <a target="_blank" href="https://cartessurtable.wixsite.com/cartes-sur-table">Cartes sur Table</a> à Gaillac</li>
<li>12 novembre 2023 à 18h30 au <a target="_blank" href="https://lescenophage.org/">Scénophage</a> à Gaillac</li>
</ul>
<div class="prochainement">Retrouvez-nous prochainement&nbsp;<span>&rarr;</span></div>
<div class="evenements">
{evenements.map(evenement => (
<Evenement evenement={evenement} />
))}
</div>
</div>
<ul role="list" class="link-card-grid">
<Card
title="Les Particules Fines"
href="/fines"
span={true}
>
Cours dimpro adultes débutant·e·s<br/>
<strong>Cest complet !</strong>
@ -42,15 +42,9 @@ import Card from '../components/Card.astro';
Écrivez-nous ou suivez-nous sur les réseaux
</Card>
</ul>
</main>
</Layout>
<style>
main {
margin: auto;
padding: 1.5rem;
max-width: 60ch;
}
h1 {
font-size: 3rem;
font-weight: 800;
@ -61,7 +55,7 @@ import Card from '../components/Card.astro';
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 400%;
background-position: 0%;
background-position: 0;
}
.instructions, .dates {
line-height: 1.6;
@ -80,20 +74,20 @@ import Card from '../components/Card.astro';
gap: 1rem;
padding: 0;
}
.dates {
color:#111;
font-size: 1.25rem;
line-height: 1.4;
.evenements {
display: flex;
flex-direction: column;
}
.dates {
box-shadow: 0.2em 0.2em 1em black;
.prochainement {
font-size: 1.2em;
font-weight: 700;
margin-bottom: 1em;
}
.dates .title {
font-weight: bold;
opacity: 0.8;
}
.dates ul {
list-style: none;
}
.dates li {
color:#444;
}
</style>

4681
yarn.lock

File diff suppressed because it is too large Load Diff