feat: integrate with contentfull
parent
3c94fcf793
commit
f067f77918
|
@ -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
|
@ -1 +0,0 @@
|
|||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
|
@ -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"
|
||||
})
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>→</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;
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>→</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 <span>→</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 d’impro adultes débutant·e·s<br/>
|
||||
<strong>C’est 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>
|
||||
|
|
Loading…
Reference in New Issue