Import repositories from gitlab
This commit is contained in:
15
next/.dockerignore
Normal file
15
next/.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
||||
/out/
|
||||
/build
|
||||
.DS_Store
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.vercel
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
5
next/.env.example
Normal file
5
next/.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
NEXT_PUBLIC_CONTENT_URI=http://127.0.0.1:1337/api
|
||||
NEXT_PUBLIC_IMG_URI=http://127.0.0.1:1337
|
||||
NEXT_PUBLIC_ORIGIN=http://localhost:3000
|
||||
NEXT_PRIVATE_CONTENT_URI=https://fefan-backend:1337/api
|
||||
NEXT_PRIVATE_IMG_URI=https://fefan-backend:1337
|
||||
3
next/.eslintrc.json
Normal file
3
next/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
38
next/.gitignore
vendored
Normal file
38
next/.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.env
|
||||
32
next/Dockerfile
Normal file
32
next/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM node:20.10-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn --frozen-lockfile
|
||||
|
||||
# Copy source for runtime build
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Build at container startup instead of build time
|
||||
RUN mkdir .next && chown nextjs:nodejs .next
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# Build and start in one step
|
||||
CMD ["sh", "-c", "yarn build && yarn start"]
|
||||
36
next/README.md
Normal file
36
next/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
19
next/api/index.js
Normal file
19
next/api/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import qs from "qs";
|
||||
|
||||
async function getData(path, query) {
|
||||
const queryString = qs.stringify(query);
|
||||
const isServerSide = typeof window === "undefined";
|
||||
const res = await fetch(
|
||||
`${
|
||||
isServerSide
|
||||
? process.env.NEXT_PRIVATE_CONTENT_URI ??
|
||||
"http://fefan-backend:1337/api"
|
||||
: process.env.NEXT_PUBLIC_CONTENT_URI ?? "https://content.fefan.fr/api"
|
||||
}/${path}?${queryString}`,
|
||||
{ cache: "no-store" }
|
||||
);
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
export default getData;
|
||||
24
next/app/contact/page.js
Normal file
24
next/app/contact/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
37
next/app/contact/style.module.scss
Normal file
37
next/app/contact/style.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
216
next/app/editions/[editionId]/page.js
Normal file
216
next/app/editions/[editionId]/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
64
next/app/editions/[editionId]/style.module.scss
Normal file
64
next/app/editions/[editionId]/style.module.scss
Normal 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
132
next/app/editions/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
12
next/app/editions/style.module.scss
Normal file
12
next/app/editions/style.module.scss
Normal 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
BIN
next/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 739 B |
44
next/app/globals.css
Normal file
44
next/app/globals.css
Normal 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
28
next/app/layout.js
Normal 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
22
next/app/legal/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
29
next/app/legal/style.module.scss
Normal file
29
next/app/legal/style.module.scss
Normal 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
114
next/app/page.js
Normal 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
84
next/app/page.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
136
next/app/prog/city-wide/page.js
Normal file
136
next/app/prog/city-wide/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
95
next/app/prog/city-wide/style.module.scss
Normal file
95
next/app/prog/city-wide/style.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
next/app/prog/village/page.js
Normal file
131
next/app/prog/village/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
40
next/app/prog/village/style.module.scss
Normal file
40
next/app/prog/village/style.module.scss
Normal 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
74
next/app/sitemap.js
Normal 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
63
next/app/sponsor/page.js
Normal 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>
|
||||
);
|
||||
}
|
||||
49
next/app/sponsor/style.module.scss
Normal file
49
next/app/sponsor/style.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
20
next/components/articleBlock/index.jsx
Normal file
20
next/components/articleBlock/index.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { BlocksRenderer } from "@strapi/blocks-react-renderer";
|
||||
import Button from "../button";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default function ArticleBlock({title, content, link, className, ...props}) {
|
||||
return (
|
||||
<article {...props} className={`${styles.articleBlock} ${className ?? ''}`}>
|
||||
<h4>{title}</h4>
|
||||
{content ? <BlocksRenderer content={content}/> : null}
|
||||
<Button
|
||||
type="secondary"
|
||||
as="a"
|
||||
href={link}
|
||||
target="_blank"
|
||||
>
|
||||
Lire la suite
|
||||
</Button>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
17
next/components/articleBlock/style.module.scss
Normal file
17
next/components/articleBlock/style.module.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.articleBlock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h4 {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-top: 1rem;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
28
next/components/button/index.jsx
Normal file
28
next/components/button/index.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default function Button({as, type, children, ...props}) {
|
||||
switch (as ?? 'a') {
|
||||
case 'a':
|
||||
return (
|
||||
<a
|
||||
tabIndex="0"
|
||||
{...props}
|
||||
className={`${styles[type ?? 'secondary']} ${styles.button} not-a-link ${props.className}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
case 'button':
|
||||
return (
|
||||
<button
|
||||
tabIndex="0"
|
||||
{...props}
|
||||
className={`${styles[type ?? 'secondary']} ${styles.button} not-a-link ${props.className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
}
|
||||
return (null);
|
||||
}
|
||||
47
next/components/button/style.module.scss
Normal file
47
next/components/button/style.module.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
.button {
|
||||
appearance: none;
|
||||
font-size: unset;
|
||||
background: unset;
|
||||
font-family: unset;
|
||||
display: inline-block;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: 2.25rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: unset;
|
||||
border: solid 1px var(--fg-2);
|
||||
|
||||
&:hover, &:focus {
|
||||
background: var(--fg-3);
|
||||
border: solid 1px var(--fg-3);
|
||||
box-shadow: 0 0.25rem 0.35rem rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: white;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.25);
|
||||
text-decoration-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-size: 250% 100%;
|
||||
transition: all ease var(--transition-time);
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration-color: var(--fefan-1);
|
||||
background-position-x: 100%;
|
||||
box-shadow: 0 0.25rem 0.35rem rgba(0, 0, 0, 0.45);
|
||||
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
32
next/components/editionElement/editionBlock/index.jsx
Normal file
32
next/components/editionElement/editionBlock/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import ImageBlock from "@/components/imageBlock";
|
||||
import StatBlock from "@/components/statBlock";
|
||||
import VideoBlock from "@/components/videoBlock";
|
||||
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default async function EditionBlock({type, ...props}) {
|
||||
|
||||
switch (type) {
|
||||
case 'stat':
|
||||
return (
|
||||
<article className={styles.editionBlock}>
|
||||
<StatBlock {...props} />
|
||||
</article>
|
||||
);
|
||||
|
||||
case 'image':
|
||||
return (
|
||||
<article className={styles.editionBlock}>
|
||||
<ImageBlock {...props} src={props.value} alt={props.title}/>
|
||||
</article>
|
||||
);
|
||||
|
||||
case 'video':
|
||||
return (
|
||||
<article className={styles.editionBlock}>
|
||||
<VideoBlock {...props} src={props.value} title={props.title}/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.editionBlock {
|
||||
overflow: hidden;
|
||||
}
|
||||
31
next/components/editionElement/index.jsx
Normal file
31
next/components/editionElement/index.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import ImageBlock from '../imageBlock';
|
||||
import EditionBlock from './editionBlock';
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default async function EditionElement(props) {
|
||||
|
||||
return (
|
||||
<section className={`${styles.editionSection} ${props.full ? styles.fullViewport : ''}`}>
|
||||
{props.href ?
|
||||
(
|
||||
<a className={styles.image} href={props.href}>
|
||||
<img src={props.flyerImg} alt={props.flyerAlt}/>
|
||||
</a>
|
||||
) : (
|
||||
<ImageBlock className={styles.image} alt={props.flyerAlt} src={props.flyerImg} />
|
||||
)
|
||||
}
|
||||
{props.blocks.slice(0, 6).map(block => (
|
||||
<EditionBlock {...block} key={`${block.type}-${block.id}`} href={props.href} />
|
||||
))}
|
||||
{props.full ? <aside className={styles.callToAction}>
|
||||
<a href={props.href}>
|
||||
<h2>{props.title}</h2>
|
||||
<svg width="40px" height="40px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12H18M18 12L13 7M18 12L13 17" stroke="var(--fefan-1)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</a>
|
||||
</aside> : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
157
next/components/editionElement/style.module.scss
Normal file
157
next/components/editionElement/style.module.scss
Normal file
@@ -0,0 +1,157 @@
|
||||
.editionSection {
|
||||
--cell-width: 14.25rem;
|
||||
|
||||
display: grid;
|
||||
scroll-snap-align: center;
|
||||
flex: none;
|
||||
grid-template-columns: repeat(4, max-content);
|
||||
grid-template-rows: repeat(2, max-content);
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
|
||||
&.fullViewport {
|
||||
min-height: calc(2 * var(--cell-width) + 10rem + 4 * var(--border-width));
|
||||
height: calc(100vh - 2 * var(--border-width) - 5rem - 2rem);
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
grid-row: span 2;
|
||||
margin: 0.5rem;
|
||||
img {
|
||||
display: block;
|
||||
height: calc(2 * var(--cell-width) + 1rem + 4 * var(--border-width));
|
||||
}
|
||||
}
|
||||
|
||||
img.image {
|
||||
display: block;
|
||||
height: calc(2 * var(--cell-width) + 1rem + 4 * var(--border-width));
|
||||
}
|
||||
|
||||
article {
|
||||
border: solid 1px var(--fg-2);
|
||||
width: var(--cell-width);
|
||||
height: var(--cell-width);
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
object-fit: fill;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.callToAction {
|
||||
grid-row: 3;
|
||||
grid-column: span 4;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > a {
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.informations {
|
||||
color: var(--fg-0);
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-left: 1rem;
|
||||
text-decoration: none;
|
||||
|
||||
h2 {
|
||||
font-family: var(--font-details);
|
||||
margin: -0.5rem 0px;
|
||||
font-size: 2rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0px;
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.75;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 70rem) {
|
||||
.editionSection {
|
||||
grid-template-columns: repeat(3, max-content);
|
||||
|
||||
.callToAction {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
article:nth-of-type(n+5) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 60rem) {
|
||||
.editionSection {
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
|
||||
.callToAction {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
article:nth-of-type(n+3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 40rem) and (min-width: 30rem) {
|
||||
.editionSection {
|
||||
grid-template-columns: repeat(1, max-content);
|
||||
|
||||
.callToAction {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
article {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 30rem) {
|
||||
.editionSection {
|
||||
--cell-width: calc(50vw - 5rem);
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
grid-template-rows: repeat(5, max-content);
|
||||
|
||||
&.fullViewport {
|
||||
height: calc(3.5 * var(--cell-width) + 10rem + 4 * var(--border-width));
|
||||
min-height: calc(100vh - 2 * var(--border-width) - 4rem - 2rem);
|
||||
}
|
||||
|
||||
.image {
|
||||
grid-row: 1;
|
||||
grid-column: span 2;
|
||||
img {
|
||||
height: auto;
|
||||
width: calc(2 * var(--cell-width) + 1rem + 4 * var(--border-width));
|
||||
}
|
||||
}
|
||||
|
||||
img.image {
|
||||
height: auto;
|
||||
width: calc(2 * var(--cell-width) + 1rem + 4 * var(--border-width));
|
||||
}
|
||||
|
||||
.callToAction {
|
||||
grid-row: 5;
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
article:nth-of-type(n+3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
next/components/editionGallery/index.jsx
Normal file
17
next/components/editionGallery/index.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import ImageBlock from "../imageBlock";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default function EditionGallery({ images }) {
|
||||
return (
|
||||
<section className={styles.gallery}>
|
||||
{images.map(({ id, attributes: attr }) => (
|
||||
<div className={styles.galleryImage} key={id}>
|
||||
<ImageBlock
|
||||
alt={attr.alternativeText}
|
||||
src={`${process.env.NEXT_PUBLIC_IMG_URI}${attr.url}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
34
next/components/editionGallery/style.module.scss
Normal file
34
next/components/editionGallery/style.module.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
.gallery {
|
||||
--gallery-width: 80rem;
|
||||
--gallery-columns: 3;
|
||||
grid-template-columns: repeat(var(--gallery-columns), calc(var(--gallery-width) / var(--gallery-columns)));
|
||||
grid-auto-rows: max-content;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.galleryImage {
|
||||
grid-column: span 1;
|
||||
margin: 0.5rem;
|
||||
max-height: calc(((6.5 / 10) * (var(--gallery-width) / var(--gallery-columns))) - 1rem);
|
||||
overflow: hidden;
|
||||
|
||||
& > img {
|
||||
width: calc((var(--gallery-width) / var(--gallery-columns)) - 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 80rem) {
|
||||
.gallery {
|
||||
--gallery-width: 90vw;
|
||||
--gallery-columns: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 50rem) {
|
||||
.gallery {
|
||||
--gallery-width: 90vw;
|
||||
--gallery-columns: 1;
|
||||
}
|
||||
}
|
||||
63
next/components/email/index.jsx
Normal file
63
next/components/email/index.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import qs from 'qs';
|
||||
import { useEffect, useState } from "react";
|
||||
import Button from "../button";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
function fetchEmail(query) {
|
||||
const queryString = qs.stringify(query);
|
||||
return fetch(`${process.env.NEXT_PUBLIC_CONTENT_URI}/site?${queryString}`).then((data) => data.json());
|
||||
}
|
||||
|
||||
export default function Email() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [displayEmail, setDisplayEmail] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const toggleEmail = () => {
|
||||
if (email === "") {
|
||||
fetchEmail({
|
||||
fields: ['contact_mail']
|
||||
}).then((res) => {
|
||||
setEmail(res.data.attributes.contact_mail);
|
||||
setDisplayEmail(true)
|
||||
})
|
||||
return;
|
||||
} else if (displayEmail === false) {
|
||||
setDisplayEmail(true)
|
||||
} else {
|
||||
navigator.clipboard.writeText(email);
|
||||
setCopied(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setDisplayEmail(false);
|
||||
}, 5000);
|
||||
}, [displayEmail])
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 2000);
|
||||
}, [copied])
|
||||
|
||||
return (
|
||||
<Button
|
||||
as="button"
|
||||
type="secondary"
|
||||
onClick={toggleEmail}
|
||||
className={styles.emailButton}
|
||||
title={displayEmail ? "Cliquez pour copier le mail" : null}
|
||||
>
|
||||
<label className={displayEmail ? styles.copy : null}>
|
||||
{displayEmail ? email : "Cliquez pour afficher le mail"}
|
||||
</label>
|
||||
<label className={copied ? styles.showLabel : styles.hideLabel}>
|
||||
{copied ? "copié" : null}
|
||||
</label>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
20
next/components/email/style.module.scss
Normal file
20
next/components/email/style.module.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
.emailButton {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
transition: all ease-in-out var(--transition-time);
|
||||
}
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.showLabel {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hideLabel {
|
||||
opacity: 0;
|
||||
}
|
||||
15
next/components/empty/index.jsx
Normal file
15
next/components/empty/index.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import Button from "../button";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default function Empty({message, ...props}) {
|
||||
return <section className={styles.empty}>
|
||||
<h2 {...props}>{message ?? "Il n'y a rien ici."}</h2>
|
||||
<Button
|
||||
as="a"
|
||||
type="secondary"
|
||||
href="/"
|
||||
>
|
||||
Retour à la page d'accueil
|
||||
</Button>
|
||||
</section>
|
||||
}
|
||||
9
next/components/empty/style.module.scss
Normal file
9
next/components/empty/style.module.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
font-family: var(--font-details);
|
||||
}
|
||||
}
|
||||
10
next/components/fanfare/index.jsx
Normal file
10
next/components/fanfare/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default async function Fanfare(props) {
|
||||
return (
|
||||
<div className={styles.band}>
|
||||
<h4>{props.name}</h4>
|
||||
<h5>{props.location}</h5>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
next/components/fanfare/style.module.scss
Normal file
22
next/components/fanfare/style.module.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
.band {
|
||||
padding: 0.75rem 0.25rem;
|
||||
max-width: 30vw;
|
||||
h4, h5 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 500;
|
||||
margin: 0px;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.75;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
14
next/components/footer/index.jsx
Normal file
14
next/components/footer/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default async function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<Link href="/legal">Mentions légales</Link>
|
||||
<span className={styles.separator}> · </span>
|
||||
<Link href="/sponsor">Ils nous soutiennent</Link>
|
||||
<span className={styles.separator}> · </span>
|
||||
<Link href="/contact">Contact</Link>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
13
next/components/footer/style.module.scss
Normal file
13
next/components/footer/style.module.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.footer {
|
||||
border-top: var(--fg-3) solid var(--border-width);
|
||||
text-align: center;
|
||||
padding: 0.25rem;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
}
|
||||
}
|
||||
34
next/components/header/index.jsx
Normal file
34
next/components/header/index.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import getData from '@/api';
|
||||
import NavigationDrawer from './navigationDrawer';
|
||||
|
||||
export default async function Header() {
|
||||
const site = await getData('site', {
|
||||
populate: {
|
||||
edition: {
|
||||
fields: [],
|
||||
populate: {
|
||||
programs: {
|
||||
fields: ['type', 'title'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const editionId = site.data?.attributes.edition.data?.id;
|
||||
const programs = editionId ? site.data.attributes.edition.data.attributes.programs.data : null
|
||||
const socialLinkQuery = editionId ? {
|
||||
filters: {
|
||||
$or: [
|
||||
{ edition: { id: { $null: true } } },
|
||||
{ edition: { id: { $eq: editionId } } },
|
||||
]
|
||||
}
|
||||
} : {
|
||||
filters: { edition: { id: { $null: true } } },
|
||||
}
|
||||
const socialLinks = await getData('social-links', socialLinkQuery)
|
||||
const links = socialLinks.data ?? []
|
||||
return (
|
||||
<NavigationDrawer programs={programs} links={links}/>
|
||||
);
|
||||
}
|
||||
46
next/components/header/navigationDrawer/index.jsx
Normal file
46
next/components/header/navigationDrawer/index.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import SocialIcon from "../social-icon";
|
||||
import { useState } from "react";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default function NavigationDrawer({programs, links , ...props}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setIsOpen(prev => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<header className={`${styles.header} ${isOpen ? styles.drawerOpen : ""}`}>
|
||||
<div className={`${styles.backdrop}`} onClick={toggleDrawer}/>
|
||||
<button className={`${styles.social} ${styles.burgerIcon}`} onClick={toggleDrawer}>
|
||||
<svg className={`feather feather-menu`} xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#1B519E" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
||||
</button>
|
||||
<Link href="/" className={`not-a-link ${styles.logo}`}>
|
||||
<img src="/fefan.png" alt="Fefan" />
|
||||
<h1>Festival de fanfare</h1>
|
||||
</Link>
|
||||
<a className={`${styles.social} ${styles.programIcon}`} href="/prog/city-wide">
|
||||
<svg className={`feather feather-calendar`} xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#1B519E" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
|
||||
</a>
|
||||
<nav className={`${styles.navigation}`}>
|
||||
<div className={styles.spacer} />
|
||||
<Link href="/">Accueil</Link>
|
||||
{programs ? programs.map(({ id, attributes: attr }) => (
|
||||
<Link href={`/prog/${attr.type}`} key={id}>{attr.title}</Link>
|
||||
)): null}
|
||||
<Link href="/editions">Éditions précédentes</Link>
|
||||
<Link href="/contact">Contact</Link>
|
||||
<div className={styles.spacer} />
|
||||
<nav className={styles.socialLinks}>
|
||||
{links.map(({ id, attributes: attr }) => (
|
||||
<Link aria-label={attr.type} target="_blank" href={attr.uri} key={id} className={`${styles.social} not-a-link`}>
|
||||
<SocialIcon name={attr.type} />
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
156
next/components/header/navigationDrawer/style.module.scss
Normal file
156
next/components/header/navigationDrawer/style.module.scss
Normal file
@@ -0,0 +1,156 @@
|
||||
.header {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 200;
|
||||
flex-flow: row;
|
||||
border-bottom: var(--fg-3) solid var(--border-width);
|
||||
background: var(--bg-0);
|
||||
}
|
||||
|
||||
.header + * {
|
||||
margin-top: calc(5rem + var(--border-width));
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin: 0.5rem 1rem;
|
||||
flex-flow: column;
|
||||
text-decoration: none;
|
||||
text-align: right;
|
||||
color: var(--fefan-4);
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-details);
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
margin: -0.10rem 0.35rem;
|
||||
}
|
||||
|
||||
img:first-child {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.social {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: unset;
|
||||
font-size: unset;
|
||||
margin: 0;
|
||||
border-radius: 100%;
|
||||
padding: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--fefan-1);
|
||||
display: flex;
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
box-shadow: inset 0 0 6px 3px var(--background);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.spacer:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
a {
|
||||
margin: 0 0.35rem;
|
||||
}
|
||||
|
||||
.socialLinks {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-right: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
content: '';
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 100vh;
|
||||
z-index: 250;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.burgerIcon {
|
||||
display: none;
|
||||
align-self: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drawerOpen .navigation {
|
||||
left: 0%;
|
||||
}
|
||||
|
||||
.drawerOpen .backdrop {
|
||||
bottom: 0vh;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.programIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.header {
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.burgerIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.programIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
padding: 1rem 0;
|
||||
flex-flow: column;
|
||||
position: fixed;
|
||||
z-index: 300;
|
||||
align-items: stretch;
|
||||
top: 0;
|
||||
max-width: 80vw;
|
||||
width: 20rem;
|
||||
left: -100%;
|
||||
bottom: 0;
|
||||
background: var(--bg-0);
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.25);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
a {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.spacer:first-of-type {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.socialLinks {
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
.drawerOpen {
|
||||
left: 0%;
|
||||
}
|
||||
}
|
||||
38
next/components/header/social-icon/index.jsx
Normal file
38
next/components/header/social-icon/index.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
export default function SocialIcon(props) {
|
||||
if (props.name === 'youtube')
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="36" height="36" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
|
||||
>
|
||||
<path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z" />
|
||||
<polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02" />
|
||||
</svg>
|
||||
);
|
||||
else if (props.name === 'instagram')
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="36" height="36" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
|
||||
>
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" ry="5" />
|
||||
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" />
|
||||
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5" />
|
||||
</svg>
|
||||
);
|
||||
else if (props.name === 'facebook')
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="36" height="36" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor"
|
||||
strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
|
||||
>
|
||||
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
0
next/components/header/style.module.scss
Normal file
0
next/components/header/style.module.scss
Normal file
33
next/components/imageBlock/index.jsx
Normal file
33
next/components/imageBlock/index.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import styles from './style.module.scss';
|
||||
import ImagePopup from '../imagePopup';
|
||||
|
||||
export default function ImageBlock({alt, src, ...props}) {
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
|
||||
const toggleShowPopup = () => {
|
||||
setShowPopup(prev => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
{...props}
|
||||
onClick={toggleShowPopup}
|
||||
src={src}
|
||||
alt={alt}
|
||||
className={`${styles.imageBlock} ${props.className}`}
|
||||
/>
|
||||
{
|
||||
showPopup ?
|
||||
<ImagePopup
|
||||
toggleShowPopup={toggleShowPopup}
|
||||
alt={alt}
|
||||
src={src}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
4
next/components/imageBlock/style.module.scss
Normal file
4
next/components/imageBlock/style.module.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.imageBlock {
|
||||
cursor: pointer;
|
||||
max-height: 100%;
|
||||
}
|
||||
32
next/components/imagePopup/index.jsx
Normal file
32
next/components/imagePopup/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default function ImagePopup({toggleShowPopup, src, alt, ...props}) {
|
||||
return (
|
||||
<div className={styles.modal}>
|
||||
<button onClick={toggleShowPopup} className={styles.close}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="36"
|
||||
height="36"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="feather feather-x"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
<div onClick={toggleShowPopup} className={styles.imgModal} />
|
||||
<img
|
||||
{...props}
|
||||
src={src}
|
||||
alt={alt}
|
||||
className={styles.popupImage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
92
next/components/imagePopup/style.module.scss
Normal file
92
next/components/imagePopup/style.module.scss
Normal file
@@ -0,0 +1,92 @@
|
||||
.modal {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 220;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: scroll;
|
||||
overscroll-behavior: contain;
|
||||
cursor: pointer;
|
||||
|
||||
& > div {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
background-color: var(--fg-0);
|
||||
opacity: 0.65;
|
||||
z-index: 220;
|
||||
}
|
||||
}
|
||||
|
||||
.popupImage {
|
||||
margin: auto;
|
||||
display: block;
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
animation-name: zoom;
|
||||
animation-duration: 0.6s;
|
||||
animation-fill-mode: both;
|
||||
z-index: 230;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
top: 3.6rem;
|
||||
right: 4rem;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
padding: 0.25rem;
|
||||
|
||||
appearance: none;
|
||||
border: unset;
|
||||
font-size: unset;
|
||||
font-family: unset;
|
||||
|
||||
color: var(--bg-0);
|
||||
background: var(--fg-0-translucid);
|
||||
font-weight: bold;
|
||||
transition: 0.3s;
|
||||
cursor: pointer;
|
||||
border-radius: 100%;
|
||||
z-index: 230;
|
||||
|
||||
svg {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
top: 3.2rem;
|
||||
right: 3.6rem;
|
||||
height: 3.8rem;
|
||||
width: 3.8rem;
|
||||
|
||||
svg {
|
||||
height: 3.8rem;
|
||||
width: 3.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zoom {
|
||||
from {
|
||||
transform:scale(0);
|
||||
box-shadow: 0 0 0.2rem rgba(0,0,0,0.5);
|
||||
}
|
||||
to {
|
||||
transform:scale(1);
|
||||
box-shadow: 0 0 1rem rgba(0,0,0,0.5);
|
||||
}
|
||||
}
|
||||
25
next/components/pressBlock/index.jsx
Normal file
25
next/components/pressBlock/index.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import ArticleBlock from "../articleBlock";
|
||||
import ImageBlock from "../imageBlock";
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default function PressBlock({
|
||||
left: {type: leftType, ...left},
|
||||
right: {type: rightType, ...right},
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<section {...props} className={styles.pressBlock}>
|
||||
{
|
||||
leftType ? leftType === 'article' ?
|
||||
<ArticleBlock {...left} className={styles.pressArticle}/> :
|
||||
<ImageBlock {...left} className={styles.pressImage}/> : null
|
||||
}
|
||||
|
||||
{
|
||||
rightType ? rightType === 'article' ?
|
||||
<ArticleBlock {...right} className={styles.pressArticle}/> :
|
||||
<ImageBlock {...right} className={styles.pressImage}/> : null
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
36
next/components/pressBlock/style.module.scss
Normal file
36
next/components/pressBlock/style.module.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
.pressBlock {
|
||||
--element-width: 30vw;
|
||||
display: flex;
|
||||
|
||||
.pressArticle, .pressImage {
|
||||
width: var(--element-width);
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.pressImage {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&:nth-of-type(2n+1) {
|
||||
flex-flow: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 80rem) {
|
||||
.pressBlock {
|
||||
--element-width: 45vw;
|
||||
|
||||
.pressImage {
|
||||
width: calc(var(--element-width) - 10vw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 60rem) {
|
||||
.pressBlock {
|
||||
--element-width: 90vw;
|
||||
&, &:nth-child(2n) {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
next/components/statBlock/index.jsx
Normal file
10
next/components/statBlock/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default async function StatBlock(props) {
|
||||
return (
|
||||
<a href={props.href} className={styles.statBox}>
|
||||
<span>{props.value}</span>
|
||||
<h3>{props.title}</h3>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
33
next/components/statBlock/style.module.scss
Normal file
33
next/components/statBlock/style.module.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
.statBox {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 4rem;
|
||||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
color: var(--fg-1);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 30rem) {
|
||||
.statBox {
|
||||
span {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
next/components/timeSlot/index.jsx
Normal file
23
next/components/timeSlot/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { BlocksRenderer } from '@strapi/blocks-react-renderer';
|
||||
import styles from './style.module.scss';
|
||||
|
||||
export default async function TimeSlot(props) {
|
||||
const date_begin = new Date(props.start)
|
||||
const date_end = new Date(props.end)
|
||||
const date_format = new Intl.DateTimeFormat('fr', {
|
||||
weekday: 'long',
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
}).format(date_begin)
|
||||
|
||||
return (
|
||||
<article className={styles.timeSlot}>
|
||||
<h4>
|
||||
{date_format} - {date_begin.getHours()}h
|
||||
{date_begin.getMinutes() !== 0 ? date_begin.getMinutes() : ""} à
|
||||
{date_end.getHours()}h{date_end.getMinutes() !== 0 ? date_end.getMinutes() : ""}
|
||||
</h4>
|
||||
<BlocksRenderer content={props.content}/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
17
next/components/timeSlot/style.module.scss
Normal file
17
next/components/timeSlot/style.module.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.timeSlot {
|
||||
h4::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-family: var(--font-details);
|
||||
margin: 0px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
color: var(--fefan-3);
|
||||
}
|
||||
|
||||
margin-bottom: 2rem;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
15
next/components/videoBlock/index.jsx
Normal file
15
next/components/videoBlock/index.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import styles from "./style.module.scss";
|
||||
|
||||
export default async function VideoBlock(props) {
|
||||
return (
|
||||
<iframe
|
||||
className={`${props.className} ${styles.videoBlock}`}
|
||||
width={ props.mode ==='thumbnail' ? "220" : "560" }
|
||||
height={props.mode ==='thumbnail' ? "220" : "315"}
|
||||
src={props.src}
|
||||
title={props.title}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
}
|
||||
3
next/components/videoBlock/style.module.scss
Normal file
3
next/components/videoBlock/style.module.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.videoBlock {
|
||||
max-width: 90vw;
|
||||
}
|
||||
7
next/jsconfig.json
Normal file
7
next/jsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
next/next.config.js
Normal file
6
next/next.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
23
next/package.json
Normal file
23
next/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "fefan-next",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/blocks-react-renderer": "^1.0.0",
|
||||
"next": "14.0.0",
|
||||
"qs": "^6.11.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"sass": "^1.69.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.0"
|
||||
}
|
||||
}
|
||||
BIN
next/public/fefan.png
Normal file
BIN
next/public/fefan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
2191
next/yarn.lock
Normal file
2191
next/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user