Import repositories from gitlab

This commit is contained in:
Julien Aldon
2026-01-19 11:43:59 +01:00
commit 68b7405c52
131 changed files with 17192 additions and 0 deletions

24
next/app/contact/page.js Normal file
View File

@@ -0,0 +1,24 @@
import getData from "@/api";
import { BlocksRenderer } from "@strapi/blocks-react-renderer";
import styles from "./style.module.scss";
import Email from "@/components/email";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
return {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: "Contactez nous !",
};
}
export default async function Contact() {
const site = await getData("site", {});
const content = site.data?.attributes.contact_text;
return (
<main className={styles.main}>
<article>{content ? <BlocksRenderer content={content} /> : null}</article>
{site.data?.attributes.contact_mail ? <Email></Email> : null}
</main>
);
}

View File

@@ -0,0 +1,37 @@
.main {
display: flex;
flex: 1 1 0%;
flex-flow: column;
align-items: center;
article {
width: 60rem;
display: flex;
flex-flow: column;
max-width: 90vw;
align-self: center;
h2, h3, h4 {
font-family: var(--font-details);
}
h2 {
align-self: center;
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
}
p {
margin: 10px;
}
}
}
@media only screen and (max-width: 641px) {
.main {
article {
text-align: justify;
}
}
}

View File

@@ -0,0 +1,216 @@
import getData from "@/api";
import styles from "./style.module.scss";
import EditionElement from "@/components/editionElement";
import EditionGallery from "@/components/editionGallery";
import PressBlock from "@/components/pressBlock";
import Empty from "@/components/empty";
import VideoBlock from "@/components/videoBlock";
import Fanfare from "@/components/fanfare";
export const dynamic = "force-dynamic";
export async function generateMetadata({ params }) {
const data = await getData(`editions/${params.editionId}`, {
populate: {
flyer: {
fields: ["name", "alternativeText", "caption", "url"],
},
},
filters: {
$or: [{ id: { $eq: params.editionId } }],
},
});
const activeEditionData = await getData("site", {
populate: {
edition: {
fields: ["id"],
},
},
fields: ["author"],
});
const activeEdition = activeEditionData?.data?.attributes.edition.data.id;
const edition = data.data?.attributes.publishedAt ? data : null;
const flyer = edition?.data?.attributes.flyer.data.attributes;
return edition
? {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: edition.title,
description: edition.description,
alternates: {
canonical:
data.data.id !== activeEdition
? `${process.env.NEXT_PUBLIC_ORIGIN}/editions/${params.editionId}`
: `${process.env.NEXT_PUBLIC_ORIGIN}`,
},
openGraph: {
title: edition.title,
url:
data.data.id !== activeEdition
? `${process.env.NEXT_PUBLIC_ORIGIN}/editions/${params.editionId}`
: `${process.env.NEXT_PUBLIC_ORIGIN}`,
description: edition.description,
images: {
url: `${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`,
width: flyer.width,
height: flyer.height,
},
authors: [activeEditionData.data.attributes.author],
type: "website",
locale: "fr_FR",
siteName: "Le Fefan - Festival de Fanfares",
},
}
: {};
}
export default async function Edition({ params }) {
const data = await getData(`editions/${params.editionId}`, {
populate: {
flyer: {
fields: ["name", "alternativeText", "caption", "url"],
},
gallery: {
fields: ["name", "alternativeText", "caption", "url"],
},
programs: {
fields: ["map_uri", "introduction", "description", "title", "type"],
populate: {
bands: {
fields: ["name", "location"],
},
},
},
statistics: {
fields: ["name", "value", "publishedAt"],
},
social_links: {
fields: ["uri", "type"],
},
articles: {
fields: ["title", "link", "excerpt", "publishedAt"],
},
fields: ["movie"],
},
filters: {
$or: [{ id: { $eq: params.editionId } }],
},
});
const edition = data.data?.attributes.publishedAt ? data : null;
const flyer = edition?.data?.attributes.flyer.data.attributes;
const gallery = edition?.data?.attributes.gallery.data;
const statistics = edition?.data?.attributes.statistics.data;
const allArticles = edition?.data?.attributes.articles.data;
const articles = allArticles
? allArticles.filter((el) => el.attributes.publishedAt != null)
: [];
const movie = edition.data.attributes.movie
? edition.data.attributes.movie
: null;
const programs = edition?.data?.attributes?.programs?.data
? edition.data.attributes.programs.data
: [];
const program =
programs.filter((el) => el.attributes.type === "city-wide")[0] ?? null;
const bands = program ? program?.attributes?.bands.data : [];
return (
<main className={styles.main}>
{edition ? (
<>
<h2>{edition.data.attributes.title}</h2>
<h3>{edition.data.attributes.subtitle}</h3>
<EditionElement
flyerImg={`${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`}
flyerAlt={flyer.alternativeText}
blocks={statistics.map(({ id, attributes }) => ({
id,
type: "stat",
title: attributes.name,
value: attributes.value,
}))}
/>
{movie && bands ? (
<section className={styles.smallProgram}>
{bands.length > 0 ? (
<article className={styles.featuring}>
<h4>Les fanfares</h4>
{bands.map(({ id, attributes: attr }) => {
return (
<Fanfare
key={id}
location={attr.location}
name={attr.name}
/>
);
})}
</article>
) : null}
{movie ? (
<>
<VideoBlock
src={movie}
title={`Aftermovie du festival ${
edition.data.attributes?.title ?? ""
}`}
/>
</>
) : null}
</section>
) : null}
{articles.length > 0 ? (
<>
<h4>Ils ont parlé de nous !</h4>
{articles.map(({ id, attributes }, index) => {
const offset = index + (articles.length - gallery.length) / 2;
return index < gallery.length ? (
<PressBlock
key={id}
left={{
type: "article",
title: attributes.title,
content: attributes.excerpt,
link: attributes.link,
}}
right={{
type: "image",
alt: gallery[index].attributes.alternativeText,
src: `${process.env.NEXT_PUBLIC_IMG_URI}${gallery[index].attributes.url}`,
}}
/>
) : (
<PressBlock
key={id}
left={{
type: "article",
title: attributes.title,
content: attributes.excerpt,
link: attributes.link,
}}
right={
articles[offset]
? {
type: "article",
title: articles[offset].title,
content: articles[offset].excerpt,
link: articles[offset].link,
}
: { type: null }
}
/>
);
})}
</>
) : null}
{articles.length <= gallery.length ? (
<>
<h4>Les photos du Fefan</h4>
<EditionGallery images={gallery.slice(articles.length)} />
</>
) : null}
</>
) : (
<Empty message="Il n'y a aucune information pour cette édition à afficher." />
)}
</main>
);
}

View File

@@ -0,0 +1,64 @@
.main {
flex: 1 1 0%;
display: flex;
align-items: center;
flex-flow: column;
padding-top: 1rem;
h2 {
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
margin: 0;
}
h3 {
font-size: 0.75rem;
color: var(--fg-1);
font-weight: 700;
text-transform: uppercase;
margin: 0;
margin-bottom: 1rem;
}
h4 {
font-family: var(--font-details);
font-size: 1.8rem;
font-weight: 400;
}
.smallProgram {
margin-top: 2rem;
display: flex;
.featuring {
h4 {
grid-column: 1 / -1;
}
display: grid;
align-self: center;
grid-template-columns: repeat(2, calc(80rem / 6));
align-self: stretch;
padding: 0 0.5rem;
justify-self: stretch;
align-items: stretch;
justify-items: stretch;
align-content: start;
grid-auto-rows: max-content;
}
}
}
@media (max-width: 60rem) {
.main {
.smallProgram {
flex-direction: column;
.featuring {
grid-template-columns: 1fr;
align-self: center;
}
}
}
}

132
next/app/editions/page.js Normal file
View File

@@ -0,0 +1,132 @@
import getData from "@/api";
import styles from "./style.module.scss";
import EditionElement from "@/components/editionElement";
import Empty from "@/components/empty";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const site = await getData("site", {
fields: ["description", "author"],
});
const data = await getData("editions", {
populate: {
gallery: {
fields: ["name", "url", "alternativeText"],
},
statistics: {
fields: ["name", "value"],
},
flyer: {
fields: ["name", "url", "alternativeText", "width", "height"],
},
},
});
const editions = data.data;
return site.data
? {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: "Editions précédentes — Le Fefan",
description: site.data.attributes.description,
alternates: {
canonical: "/editions",
},
openGraph: {
title: "Editions précédentes — Le Fefan",
url: `${process.env.NEXT_PUBLIC_ORIGIN}/prog/city-wide`,
description: site.data.attributes.description,
images: editions.map(({ attributes: attr }) => ({
width: attr.flyer.data.attributes.width,
height: attr.flyer.data.attributes.height,
alt: attr.flyer.data.attributes.alternativeText,
url: attr.flyer.data.attributes.url,
})),
authors: [site.data.attributes.author],
type: "website",
locale: "fr_FR",
siteName: "Le Fefan - Festival de Fanfares",
},
}
: {};
}
export default async function Editions() {
const site = await getData("site", {
fields: ["id"],
populate: {
edition: {
fields: ["id"],
},
},
});
const data = await getData("editions", {
populate: {
gallery: {
fields: ["name", "url", "alternativeText"],
},
statistics: {
fields: ["name", "value"],
},
flyer: {
fields: ["name", "url", "alternativeText"],
},
fields: ["movie"],
},
});
const editions = (data.data ?? []).filter(
(e) => e.id !== site.data?.attributes.edition.data?.id
);
return (
<main className={styles.main}>
{editions ? (
editions.map(({ id, attributes: attr }) => {
const stats = attr.statistics.data.map(({ id, attributes }) => ({
id,
type: "stat",
title: attributes.name,
value: attributes.value,
}));
const images = attr.gallery.data.map(({ id, attributes }) => ({
id,
type: "image",
title: attributes.alternativeText,
value: `${process.env.NEXT_PUBLIC_IMG_URI}${attributes.url}`,
}));
const movie = attr.movie
? [
{
id: "movie",
type: "video",
title: `Aftermovie ${attr.title}`,
value: attr.movie,
mode: "thumbnail",
},
]
: null;
return (
<EditionElement
full
key={id}
href={`/editions/${id}`}
title={attr.title}
blocks={
movie
? stats.concat(movie).concat(images)
: stats.concat(images)
}
flyerImg={`${process.env.NEXT_PUBLIC_IMG_URI}${attr.flyer.data.attributes.url}`}
flyerAlt={attr.flyer.data.attributes.alternativeText}
/>
);
})
) : (
<Empty message="Il n'y a aucune édition à afficher."></Empty>
)}
</main>
);
}

View File

@@ -0,0 +1,12 @@
.main {
flex: 1 1 0%;
display: flex;
flex-flow: column;
align-items: center;
height: 100vh;
overflow: auto;
scroll-snap-type: y mandatory;
// .editionLink {
// }
}

BIN
next/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

44
next/app/globals.css Normal file
View File

@@ -0,0 +1,44 @@
:root {
--bg-0: #FFFFCC;
--fg-0: #412D0A;
--fg-0-translucid: rgba(65, 45, 10, 0.35);
--fg-1: #957948;
--fg-2: #CCC08F;
--fg-3: #DDDDAA;
--fefan-1: #1B519E;
--fefan-2: #295DAB;
--fefan-3: #32A1DA;
--fefan-4: #B8529F;
--transition-time: 0.2s;
--font-body: 'Krub', sans-serif;
--font-details: 'Shadows Into Light', sans-serif;
--border-width: 1px;
}
html, body {
background: var(--bg-0);
color: var(--fg-0);
height: 100vh;
margin: 0;
font-family: var(--font-body);
display: flex;
flex-direction: column;
}
a:not(.not-a-link) {
text-decoration-color: transparent;
color: transparent;
background: linear-gradient(175deg, var(--fefan-1) 0%, var(--fefan-1) 45%, var(--fefan-2) 55%, var(--fefan-3) 70%, var(--fefan-4) 85%);
background-clip: text;
background-size: 250% 100%;
transition: all ease var(--transition-time);
&:hover {
text-decoration-color: var(--fefan-1);
background-position-x: 100%;
}
&:visited {
color: unset;
}
}

28
next/app/layout.js Normal file
View File

@@ -0,0 +1,28 @@
import './globals.css'
import Header from '@/components/header'
import Footer from '@/components/footer'
export const dynqmic = 'force-dynamic'
export const metadata = {
generator: 'Next.js',
applicationName: 'Le Fefan',
keywords: ['Fefan', 'Festival', 'Fanfare', 'Cuivres', 'Villeurbanne', 'Evenement', 'Musique'],
authors: [{ name: 'Girasol' }],
creator: 'Girasol',
}
export default function RootLayout({ children }) {
return (
<html lang="fr">
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Krub:wght@500;700&family=Shadows+Into+Light&display=swap" />
</head>
<body>
<Header />
{children}
<Footer />
</body>
</html>
)
}

22
next/app/legal/page.js Normal file
View File

@@ -0,0 +1,22 @@
import getData from "@/api";
import { BlocksRenderer } from "@strapi/blocks-react-renderer";
import styles from "./style.module.scss";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
return {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: "Mentions légales",
};
}
export default async function Legal() {
const site = await getData("site", {});
const content = site.data?.attributes.legal;
return (
<main className={styles.main}>
<article>{content ? <BlocksRenderer content={content} /> : null}</article>
</main>
);
}

View File

@@ -0,0 +1,29 @@
.main {
display: flex;
flex: 1 1 0%;
flex-flow: column;
align-items: center;
article {
width: 60rem;
display: flex;
flex-flow: column;
max-width: 90vw;
align-self: center;
h2, h3, h4 {
font-family: var(--font-details);
}
h2 {
align-self: center;
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
}
p {
margin: 0px;
}
}
}

114
next/app/page.js Normal file
View File

@@ -0,0 +1,114 @@
import getData from "@/api";
import styles from "./page.module.scss";
import { BlocksRenderer } from "@strapi/blocks-react-renderer";
import Button from "@/components/button";
import ImageBlock from "@/components/imageBlock";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const site = await getData("site", {
populate: {
edition: {
populate: {
flyer: {
fields: [
"name",
"url",
"alternativeText",
"caption",
"width",
"height",
],
},
programs: true,
},
fields: ["id", "introduction", "title", "subtitle", "description"],
},
},
fields: ["author"],
});
const current_edition = site.data?.attributes.edition.data?.attributes;
const flyer =
site.data?.attributes.edition.data?.attributes.flyer.data.attributes;
return current_edition
? {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: current_edition.title,
description: current_edition.description,
alternates: {
canonical: "/",
},
openGraph: {
title: current_edition.title,
url: `${process.env.NEXT_PUBLIC_ORIGIN}`,
description: current_edition.description,
images: {
url: `${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`,
width: flyer.width,
height: flyer.height,
},
authors: [site.data.attributes.author],
type: "website",
locale: "fr_FR",
siteName: "Le Fefan - Festival de Fanfares",
},
}
: {};
}
export default async function Home() {
const site = await getData("site", {
populate: {
edition: {
populate: {
flyer: {
fields: ["name", "url", "alternativeText", "caption"],
},
programs: true,
},
fields: ["id", "introduction", "title", "subtitle"],
},
},
});
const current_edition = site.data?.attributes.edition.data?.attributes;
const flyer =
site.data?.attributes.edition.data?.attributes.flyer.data.attributes;
const introduction = current_edition?.introduction;
const programs = current_edition?.programs.data;
return (
<main className={styles.main}>
{current_edition ? (
<section className={styles.home}>
{flyer ? (
<ImageBlock
className={styles.flyer}
alt={flyer.alternativeText}
src={`${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`}
/>
) : null}
<article>
<h2>{current_edition.title}</h2>
<h3>{current_edition.subtitle}</h3>
{introduction ? <BlocksRenderer content={introduction} /> : null}
<nav>
{programs.filter((e) => e.attributes.type === "city-wide")
.length > 0 ? (
<Button as="a" type="primary" href="/prog/city-wide">
Voir la programmation
</Button>
) : null}
{programs.filter((e) => e.attributes.type === "village").length >
0 ? (
<Button as="a" type="secondary" href="/prog/village">
Le Village
</Button>
) : null}
</nav>
</article>
</section>
) : null}
</main>
);
}

84
next/app/page.module.scss Normal file
View File

@@ -0,0 +1,84 @@
.main {
flex: 1;
display: flex;
flex-flow: column;
.home {
margin-top: 2rem;
display: flex;
width: 60rem;
max-width: 90vw;
align-items: flex-start;
align-self: center;
article {
padding-left: 1rem;
}
.flyer {
width: 20rem;
max-width: 30vw;
}
nav {
margin-bottom: 1rem;
}
p {
}
h2 {
font-family: var(--font-details);
margin: -0.5rem 0;
font-size: 2rem;
font-weight: 400;
}
h3 {
font-size: 0.75rem;
color: var(--fg-1);
font-weight: 700;
text-transform: uppercase;
margin: 0;
}
}
}
@media only screen and (max-width: 992px) {
.main {
.home {
flex-flow: column;
align-items: center;
article {
padding-left: 0px;
}
.flyer {
width: 50vw;
max-width: 50vw;
}
}
}
}
@media only screen and (max-width: 641px) {
.main {
.home {
article {
text-align: justify;
}
.flyer {
width: 90vw;
max-width: 90vw;
padding-bottom: 2rem;
padding-bottom: 2rem;
}
nav {
display: flex;
align-items: center;
flex-flow: column;
}
}
}
}

View File

@@ -0,0 +1,136 @@
import getData from "@/api";
import styles from "./style.module.scss";
import Fanfare from "@/components/fanfare";
import TimeSlot from "@/components/timeSlot";
import Empty from "@/components/empty";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const site = await getData("site", {
populate: {
edition: {
populate: {
flyer: {
fields: [
"name",
"url",
"alternativeText",
"caption",
"width",
"height",
],
},
programs: true,
},
fields: ["id", "introduction", "title", "subtitle"],
},
},
fields: ["author"],
});
const current_edition = site.data?.attributes.edition.data?.attributes;
const flyer =
site.data?.attributes.edition.data?.attributes.flyer.data.attributes;
const programs = current_edition?.programs.data;
const cityWide = programs?.filter(
(e) => e.attributes.type === "city-wide"
)[0];
return cityWide
? {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: cityWide.attributes.title,
description: cityWide.attributes.description,
alternates: {
canonical: "/prog/city-wide",
},
openGraph: {
title: cityWide.attributes.title,
url: `${process.env.NEXT_PUBLIC_ORIGIN}/prog/city-wide`,
description: cityWide.attributes.description,
images: {
url: `${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`,
width: flyer.width,
height: flyer.height,
},
authors: [site.data.attributes.author],
type: "website",
locale: "fr_FR",
siteName: "Le Fefan - Festival de Fanfares",
},
}
: {};
}
export default async function CityWide() {
const site = await getData("site", {
populate: {
edition: {
populate: {
programs: {
populate: {
bands: {
fields: ["*"],
},
time_slots: {
fields: ["*"],
},
},
},
},
},
},
});
const edition = site.data?.attributes.edition ?? {};
const program =
edition.data?.attributes.programs.data.filter(
({ attributes: r }) => r.type === "city-wide"
)[0] ?? {};
const time_slots = program.attributes?.time_slots.data ?? [];
return (
<main className={styles.main}>
{program.attributes ? (
<>
<h2>{program.attributes.title}</h2>
<section className={styles.program}>
{time_slots.map(({ id, attributes: attr }) => {
return (
<TimeSlot
key={id}
content={attr.content}
start={attr.start}
end={attr.end}
/>
);
})}
</section>
<section className={styles.program}>
<article className={styles.featuring}>
{program.attributes.bands.data.length === 0 ? null : (
<h3>Les Fanfares</h3>
)}
<p className={styles.introduction}>
{program.attributes.description}
</p>
{program.attributes.bands.data.map(({ id, attributes: attr }) => {
return (
<Fanfare key={id} location={attr.location} name={attr.name} />
);
})}
</article>
<iframe
className={styles.map}
src={program.attributes.map_uri}
width={640}
height={480}
></iframe>
</section>
<section className={styles.program}></section>
</>
) : (
<Empty message="Pas de programmation à afficher pour le moment"></Empty>
)}
</main>
);
}

View File

@@ -0,0 +1,95 @@
.main {
display: flex;
flex: 1 1 0%;
flex-flow: column;
.program {
display: grid;
width: 80rem;
max-width: 90vw;
align-self: center;
grid-template-columns: repeat(3, calc(80rem / 3));
grid-auto-rows: max-content;
&+.program {
margin-top: 2rem;
}
h3 {
grid-column: 1 / -1;
font-family: var(--font-details);
margin: 0.25rem 0px;
font-size: 2rem;
font-weight: 400;
justify-self: stretch;
}
}
.map {
width: auto;
grid-column: span 2;
place-self: stretch;
border: medium;
}
.featuring {
display: grid;
grid-template-columns: repeat(2, calc(80rem / 6));
place-self: stretch;
padding: 0px 0.5rem;
place-items: stretch;
align-content: start;
grid-auto-rows: max-content;
h3 {
font-family: var(--font-details);
grid-column: 1 / -1;
margin: 0.25rem 0px;
font-size: 2rem;
font-weight: 400;
justify-self: stretch;
max-width: 80vw;
}
.introduction {
grid-column: 1 / -1;
justify-self: stretch;
text-align: left;
max-width: 90vw;
padding-right: 2rem;
}
p {
margin-top: 0px;
}
}
h2 {
align-self: center;
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
}
}
@media only screen and (max-width: 80rem) {
.main {
.program {
grid-template-columns: repeat(2, calc(50vw - 3rem));
}
}
}
@media only screen and (max-width: 60rem) {
.main {
.program {
grid-template-columns: repeat(1, 90vw);
}
.featuring {
.introduction {
padding-right: unset;
}
}
}
}

View File

@@ -0,0 +1,131 @@
import Empty from "@/components/empty";
import styles from "./style.module.scss";
import getData from "@/api";
import ImageBlock from "@/components/imageBlock";
import { BlocksRenderer } from "@strapi/blocks-react-renderer";
import TimeSlot from "@/components/timeSlot";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
const site = await getData("site", {
populate: {
edition: {
populate: {
flyer: {
fields: [
"name",
"url",
"alternativeText",
"caption",
"width",
"height",
],
},
programs: true,
},
fields: ["id", "introduction", "title", "subtitle"],
},
},
fields: ["author"],
});
const current_edition = site.data?.attributes.edition.data?.attributes;
const flyer =
site.data?.attributes.edition.data?.attributes.flyer.data.attributes;
const programs = current_edition?.programs.data;
const village = programs?.filter((e) => e.attributes.type === "village")[0];
return village
? {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: village.attributes.title,
description: village.attributes.description,
alternates: {
canonical: "/prog/village",
},
openGraph: {
title: village.attributes.title,
url: `${process.env.NEXT_PUBLIC_ORIGIN}/prog/village`,
description: village.attributes.description,
images: {
url: `${process.env.NEXT_PUBLIC_IMG_URI}${flyer.url}`,
width: flyer.width,
height: flyer.height,
},
authors: [site.data.attributes.author],
type: "website",
locale: "fr_FR",
siteName: "Le Fefan - Festival de Fanfares",
},
}
: {};
}
export default async function Village() {
const site = await getData("site", {
populate: {
edition: {
populate: {
programs: {
populate: {
sticker: {
fields: ["alternativeText", "url"],
},
bands: {
fields: ["*"],
},
time_slots: {
fields: ["*"],
},
},
},
},
},
},
});
const edition = site.data?.attributes.edition ?? {};
const program =
edition.data?.attributes.programs.data.filter(
({ attributes: r }) => r.type === "village"
)[0] ?? {};
const time_slots = program.attributes?.time_slots.data ?? [];
return (
<main className={styles.main}>
{program.attributes ? (
<>
<h2>{program.attributes?.title ?? "Le Village"}</h2>
<section className={styles.villageSection}>
<article>
{program.attributes?.introduction ? (
<BlocksRenderer content={program.attributes.introduction} />
) : null}
</article>
{program.attributes?.sticker?.data ? (
<ImageBlock
src={`${process.env.NEXT_PUBLIC_IMG_URI}${program.attributes.sticker.data.attributes.url}`}
alt={program.attributes.sticker.data.attributes.alternativeText}
className={styles.villageImg}
/>
) : (
<div></div>
)}
<article>
{time_slots.map(({ id, attributes: attr }) => {
return (
<TimeSlot
key={id}
content={attr.content}
start={attr.start}
end={attr.end}
/>
);
})}
</article>
</section>
</>
) : (
<Empty message="Le village est encore secret"></Empty>
)}
</main>
);
}

View File

@@ -0,0 +1,40 @@
.main {
flex: 1;
display: flex;
flex-flow: column;
h2 {
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
text-align: center;
margin: 1rem 0;
}
.villageSection {
display: flex;
flex-flow: row;
width: 60rem;
align-self: center;
.villageImg {
width: 18rem;
align-self: center;
flex: 0;
}
article {
flex: 1;
padding: 0 0.5rem;
}
}
}
@media (max-width: 60rem) {
.main {
.villageSection {
flex-flow: column;
width: 96vw;
}
}
}

74
next/app/sitemap.js Normal file
View File

@@ -0,0 +1,74 @@
import getData from "@/api";
export default async function sitemap() {
// Main page information
const site = await getData("site", {
fields: ["updatedAt"],
populate: {
edition: {
fields: ["id", "updatedAt", "publishedAt"],
populate: {
programs: true,
fields: ["type", "updatedAt", "publishedAt"],
},
},
},
});
const edition = site.data?.attributes.edition.data;
const siteLastModified =
site && edition
? new Date(
Math.max(
new Date(site.data?.attributes.updatedAt),
new Date(edition.attributes.updatedAt)
)
)
: new Date();
// Programs information
const programs = (edition?.attributes.programs.data ?? []).filter(
(p) => p.attributes.publishedAt !== null
);
// Editions information
const editions = await getData("editions", { fields: ["id", "updatedAt"] });
const previousEditions = editions.data?.filter(
(e) => e.id !== edition.id && e.attributes.publishedAt !== null
);
const editionsLastModifiedTimestamp = Math.max(
...(previousEditions ?? []).map((e) => new Date(e.attributes.updatedAt))
);
const editionsLastModified =
editionsLastModifiedTimestamp === -Infinity
? siteLastModified
: new Date(editionsLastModifiedTimestamp);
return [
{
url: `${process.env.NEXT_PUBLIC_ORIGIN}`,
lastModified: siteLastModified,
changeFrequency: "yearly",
prority: 1,
},
...(programs ?? []).map((p) => ({
url: `${process.env.NEXT_PUBLIC_ORIGIN}/prog/${p.attributes.type}`,
lastModified: new Date(p.attributes.updatedAt),
changeFrequency: "yearly",
priority: 0.8,
})),
{
url: `${process.env.NEXT_PUBLIC_ORIGIN}/editions`,
lastModified: editionsLastModified,
changeFrequency: "yearly",
priority: 0.6,
},
...(previousEditions ?? []).map((e) => ({
url: `${process.env.NEXT_PUBLIC_ORIGIN}/editions/${e.id}`,
lastModified: new Date(e.attributes.updatedAt),
changeFrequency: "yearly",
priority: 0.5,
})),
];
}

63
next/app/sponsor/page.js Normal file
View File

@@ -0,0 +1,63 @@
import getData from "@/api";
import styles from "./style.module.scss";
import Link from "next/link";
import Empty from "@/components/empty";
export const dynamic = "force-dynamic";
export async function generateMetadata() {
return {
metadataBase: `${process.env.NEXT_PUBLIC_ORIGIN}`,
title: "Partenaires",
};
}
export default async function Sponsor() {
const site = await getData("site", {
populate: {
edition: {
fields: [],
populate: {
sponsors: {
fields: ["name", "uri", "image"],
populate: {
image: {
fields: ["alternativeText", "name", "url"],
},
},
},
},
},
},
});
const edition = site.data?.attributes.edition.data.attributes;
const sponsors = edition?.sponsors.data;
return (
<main className={styles.main}>
<h2>Ils nous soutiennent</h2>
{sponsors && sponsors.length > 0 ? (
<section className={styles.sponsors}>
{sponsors.map(({ id, attributes: attr }) => {
return (
<figure key={id}>
<Link target="_blank" href={attr.uri}>
<img
alt={attr.image.data.attributes.alternativeText}
src={`${process.env.NEXT_PUBLIC_IMG_URI}${attr.image.data.attributes.url}`}
/>
</Link>
<figcaption>
<Link target="_blank" href={attr.uri}>
{attr.name}
</Link>
</figcaption>
</figure>
);
})}
</section>
) : (
<Empty message="Pas de sponsors à afficher pour l'édition en cours" />
)}
</main>
);
}

View File

@@ -0,0 +1,49 @@
.main {
flex: 1 1 0%;
display: flex;
flex-flow: column;
h2 {
align-self: center;
font-family: var(--font-details);
font-size: 2rem;
font-weight: 400;
}
}
.sponsors {
display: grid;
grid-template-columns: repeat(4, 20rem);
place-items: end center;
align-self: center;
figure {
padding: 1rem 0px;
}
figcaption {
padding: 0.25rem 0px;
text-align: center;
}
img {
width: 13.5rem;
}
}
.sponsors figure:last-child {
grid-column-start: span 4;
}
@media only screen and (max-width: 70rem) {
.sponsors {
grid-template-columns: 80vw;
img {
width: calc(80vw - 20rem);
}
}
.sponsors figure:last-child {
grid-column-start: unset;
}
}