Skip to content

Commit 40440e7

Browse files
committed
feat: add ticketing for 2026
1 parent 364aa5e commit 40440e7

File tree

10 files changed

+381
-41
lines changed

10 files changed

+381
-41
lines changed

pwa/app/(con)/[locale]/con/2025/tickets/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
2525
},
2626
alternates: {
2727
languages: {
28-
en: locale === "en" ? undefined : "/con/2025/call-for-papers",
29-
fr: locale === "fr" ? undefined : "/fr/con/2025/call-for-papers",
28+
en: locale === "en" ? undefined : "/con/2025/tickets",
29+
fr: locale === "fr" ? undefined : "/fr/con/2025/tickets",
3030
},
3131
},
3232
};

pwa/app/(con)/[locale]/con/2026/components/HomePage.tsx

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { LanguageContext } from "contexts/con/LanguageContext";
1414
import Section from "components/con/home/Section";
1515
import PictureGallery from "components/con/common/PictureGallery";
1616
import AfterMovie from "../../2025/components/AfterMovie";
17+
import BuyButton from "components/con/common/BuyButton";
18+
import { currentEdition } from "data/con/editions";
19+
import prices from "data/con/2026/prices";
20+
import PricingCard from "components/con/home/Pricing/PricingCard";
1721

1822
type HomePageProps = {
1923
speakers: Speaker[];
@@ -42,8 +46,13 @@ const HomePage = ({ speakers, partners, images }: HomePageProps) => {
4246
{t("2026.baseline")}
4347
</p>
4448
<div className="flex gap-2">
49+
{currentEdition === "2026" && (
50+
<BuyButton className="mr-2" id="cover">
51+
{t("buy_tickets")}
52+
</BuyButton>
53+
)}
4554
<Button
46-
className="pink"
55+
className="empty"
4756
to={`/${locale}/con/2026/call-for-papers`}
4857
>
4958
{t("2026.cfp.button")}
@@ -91,38 +100,9 @@ const HomePage = ({ speakers, partners, images }: HomePageProps) => {
91100
</PictureGallery>
92101
</div>
93102
</Section>
94-
<div className="pb-12">
95-
<AfterMovie />
96-
</div>
97-
<Section
98-
section="missing"
99-
className="relative z-10 text-center overflow-y-clip"
100-
>
101-
<div className="container text-center">
102-
<SectionTitle dark>
103-
<Translate
104-
translationKey="missing_conferences.title"
105-
translationParams={{ edition: "2025" }}
106-
/>
107-
</SectionTitle>
108-
<SectionSubTitle dark>
109-
<Translate
110-
translationKey="2026.missing_conferences.subtitle"
111-
translationParams={{ link: t("2026.missing_conferences.link") }}
112-
/>
113-
</SectionSubTitle>
114-
<Button
115-
className="mx-auto mb-10"
116-
external
117-
to="https://www.youtube.com/playlist?list=PL3hoUDjLa7eSppJSvwSIeBexYZQWkN0bm"
118-
>
119-
{t("2026.missing_conferences.subscribe")}
120-
</Button>
121-
</div>
122-
</Section>
123103
<Section
124104
section="speakers"
125-
className="bg-white z-10 relative py-4 overflow-x-hidden"
105+
className="bg-grey z-10 relative py-4 overflow-x-hidden"
126106
>
127107
<div className="container text-center">
128108
<SectionTitle h1>
@@ -155,6 +135,44 @@ const HomePage = ({ speakers, partners, images }: HomePageProps) => {
155135
) : null}
156136
</div>
157137
</Section>
138+
{currentEdition === "2026" && (
139+
<Section
140+
className="relative py-10 before:bg-grey before:h-[calc(100%-500px)] before:absolute before:left-0 before:bottom-0 before:w-full after:bg-wave2 after:w-[1300px] after:h-[800px] after:absolute after:top-24 after:left-1/2 after:bg-top after:bg-contain after:opacity-50 after:bg-no-repeat after:-translate-x-1/2 after:rotate-6"
141+
section="pricing"
142+
>
143+
<div className="container relative z-10">
144+
<SectionTitle dark>
145+
<Translate translationKey="pricing.title" />
146+
</SectionTitle>
147+
<div className="max-w-4xl mx-auto flex flex-row flex-wrap justify-center">
148+
{prices.map((price) => (
149+
<PricingCard key={price.id} price={price} />
150+
))}
151+
<div className="w-full self-center max-w-md mt-10 | lg:pl-10 lg:mt-0 lg:w-1/3">
152+
<div className="p-5 dotted-corner flex flex-col items-center text-center bg-blue bg-blue-gradient shadow-md border-blue-dark border-4">
153+
<span className="font-bold text-white leading-tight font-title uppercase lined-center lined-white relative">
154+
{t("pricing.student")}
155+
</span>
156+
<div className="mt-2 text-blue-black/80 font-semibold">
157+
<Translate translationKey="pricing.free_ticket" />
158+
</div>
159+
<Button
160+
size="small"
161+
square
162+
className="white mt-2 mb-5"
163+
to="mailto:events@les-tilleuls.coop"
164+
>
165+
{t("contact_us")}
166+
</Button>
167+
<small className="text-xs text-blue-black/50 font-bold">
168+
*{t("pricing.certificate_needed")}
169+
</small>
170+
</div>
171+
</div>
172+
</div>
173+
</div>
174+
</Section>
175+
)}
158176
<Venue subtitle={t("2026.venue.subtitle")} />
159177
<Section section="sponsorship" className="py-8">
160178
<div className="container text-center">

pwa/app/(con)/[locale]/con/2026/layout.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
4141
function EditionLayout({ children }: { children: React.ReactNode }) {
4242
const eventData = getEditionEventData("2026");
4343
return (
44-
<LayoutBase
45-
edition="2026"
46-
nav={nav}
47-
footer={footer}
48-
isTicketingOpen={false}
49-
>
44+
<LayoutBase edition="2026" nav={nav} footer={footer} isTicketingOpen>
5045
<script
5146
type="application/ld+json"
5247
dangerouslySetInnerHTML={{ __html: JSON.stringify(eventData) }}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"use client";
2+
import { useContext, useEffect } from "react";
3+
import { LanguageContext } from "contexts/con/LanguageContext";
4+
import SectionTitle from "components/con/common/typography/SectionTitle";
5+
import SectionSubTitle from "components/con/common/typography/SectionSubtitle";
6+
import Script from "next/script";
7+
import prices from "data/con/2026/prices";
8+
import { Offer } from "types/con";
9+
import dayjs from "dayjs";
10+
import classNames from "classnames";
11+
import { toLocaleDate } from "utils/con";
12+
13+
export default function RegisterPage() {
14+
const { t, Translate, getLocaleDictionary } = useContext(LanguageContext);
15+
const timelinePrices = prices
16+
.find((p) => p.id === 1)
17+
?.offers.filter((o) => o.type);
18+
19+
const isActiveOffer = (offer: Offer) => {
20+
if (offer.limitDate && dayjs(offer.limitDate).isBefore(dayjs(), "day"))
21+
return false;
22+
if (offer.startDate && dayjs(offer.startDate).isAfter(dayjs(), "day"))
23+
return false;
24+
return true;
25+
};
26+
27+
const isPastOffer = (offer: Offer) => {
28+
if (isActiveOffer(offer)) return false;
29+
if (offer.startDate && dayjs(offer.startDate).isBefore(dayjs(), "day"))
30+
return true;
31+
if (!offer.startDate) return true;
32+
return false;
33+
};
34+
35+
const expectations =
36+
getLocaleDictionary?.()[2026].tickets.expect.points || [];
37+
38+
useEffect(() => {
39+
const iframe = document.getElementById(
40+
"yurplan-widget-141690"
41+
) as HTMLIFrameElement | null;
42+
if (!iframe) return;
43+
const handleLoad = () => {
44+
const loader = document.getElementById("loader");
45+
loader?.classList.add("hidden");
46+
};
47+
iframe.addEventListener("load", handleLoad, true);
48+
}, []);
49+
50+
return (
51+
<>
52+
<div className="container max-w-5xl flex flex-col items-center py-12 relative z-10">
53+
<SectionTitle small h1 dark lined>
54+
<Translate translationKey="2026.tickets.title" />
55+
</SectionTitle>
56+
<SectionSubTitle dark>{t("2026.tickets.subtitle")}</SectionSubTitle>
57+
</div>
58+
<div className="">
59+
<div className="container max-w-6xl relative z-10">
60+
<div className="flex flex-col lg:flex-row w-full max-w-6xl items-center lg:items-start">
61+
<div className="translate-y-12 relative z-10 w-4/5 lg:w-2/5 max-w-md before:absolute before:w-full before:h-full before:bg-blue before:-translate-x-3 before:-translate-y-3 before:left-0 before:top-0">
62+
<img
63+
className="relative"
64+
src="/images/con/2025/review/pic-01.jpg"
65+
alt=""
66+
/>
67+
</div>
68+
<div className="flex-1 relative bg-white shadow-floating dotted-corner p-12 pt-24 lg:pt-12 lg:pl-24 lg:-translate-x-12 leading-relaxed font-light">
69+
<Translate translationKey="2026.tickets.description" />
70+
<p className="mt-4 text-lg font-bold">
71+
{t("2026.tickets.description2")}
72+
</p>
73+
</div>
74+
</div>
75+
</div>
76+
</div>
77+
<div className="">
78+
<div className="container relative z-10 max-w-4xl py-12">
79+
<SectionTitle dark small lined>
80+
<strong>{t("2026.tickets.buy")}</strong>
81+
</SectionTitle>
82+
<div className="hidden relative w-full gap-0 md:grid grid-cols-3 py-12 mb-12 overflow-hidden">
83+
<div className="absolute w-1.5 h-full left-4 -translate-x-1/2 md:-translate-x-0 top-0 md:h-1.5 md:w-full md:top-1/2 md:left-0 md:-translate-y-1/2 bg-white/30"></div>
84+
{timelinePrices?.map((p) => (
85+
<div
86+
key={p.type}
87+
className="relative flex flex-col items-center gap-2"
88+
>
89+
<div className="ml-8 md:ml-0 whitespace-nowrap absolute left-full md:left-1/2 top-1/2 -translate-y-1/2 -translate-x-1/2">
90+
<div
91+
className={classNames(
92+
"font-bold uppercase md:mb-12 text-left md:text-center",
93+
isActiveOffer(p)
94+
? "text-blue md:mb-12"
95+
: isPastOffer(p)
96+
? "text-white/30 md:mb-4"
97+
: "text-white md:mb-4"
98+
)}
99+
>
100+
{p.type}
101+
</div>
102+
<p
103+
className={classNames(
104+
isActiveOffer(p)
105+
? "text-blue"
106+
: isPastOffer(p)
107+
? "text-white/30"
108+
: "text-white"
109+
)}
110+
>
111+
{t("2026.tickets.until_date", {
112+
date: toLocaleDate(p.limitDate as string),
113+
})}
114+
</p>
115+
</div>
116+
<div
117+
className={classNames(
118+
"relative rounded-full border-4 size-8",
119+
isActiveOffer(p)
120+
? "border-blue bg-blue before:h-screen before:w-1.5 before:md:w-screen before:absolute before:md:h-1.5 before:bottom-full before:-translate-x-1/2 before:md:-translate-x-0 before:md:right-full before:bg-blue before:left-1/2 before:md:left-auto before:md:top-1/2 before:md:-translate-y-1/2"
121+
: "hidden"
122+
)}
123+
/>
124+
</div>
125+
))}
126+
</div>
127+
<a
128+
title="Vente de billets en ligne"
129+
href="https://www.billetweb.fr/shop.php?event=api-platform-conference-2026"
130+
className="shop_frame"
131+
target="_blank"
132+
data-src="https://www.billetweb.fr/shop.php?event=api-platform-conference-2026"
133+
data-max-width="100%"
134+
data-initial-height="600"
135+
data-scrolling="no"
136+
data-id="api-platform-conference-2026"
137+
data-resize="1"
138+
>
139+
Vente de billets en ligne
140+
</a>
141+
<Script
142+
src="https://www.billetweb.fr/js/export.js"
143+
strategy="afterInteractive"
144+
/>
145+
</div>
146+
</div>
147+
<div className="bg-white pt-12 pb-48">
148+
<div className="container max-w-6xl -mt-12">
149+
<SectionTitle small lined>
150+
<Translate translationKey="2026.tickets.expect.title" />
151+
</SectionTitle>
152+
<div className="mx-auto max-w-64 sm:max-w-xl grid grid-cols-1 sm:grid-cols-2 xl:max-w-none xl:grid-cols-4 gap-8 text-white">
153+
{expectations.map((e, i) => (
154+
<div
155+
key={i}
156+
className={classNames(
157+
"p-8 aspect-square flex flex-col justify-center",
158+
i === 0 && "bg-blue-dark",
159+
i === 1 && "bg-blue-black/80",
160+
i === 2 && "bg-blue-darkest",
161+
i === 3 && "bg-blue-dark"
162+
)}
163+
>
164+
<p className="uppercase font-bold text-xl">{e.title}</p>
165+
<p>{e.text}</p>
166+
</div>
167+
))}
168+
</div>
169+
</div>
170+
</div>
171+
</>
172+
);
173+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Locale, i18n } from "i18n/i18n-config";
2+
import { Metadata } from "next";
3+
import RegisterPage from "./RegisterPage";
4+
5+
type Props = {
6+
params: { locale: Locale };
7+
};
8+
export async function generateMetadata({ params }: Props): Promise<Metadata> {
9+
const locale = params.locale || i18n.defaultLocale;
10+
const dictionary = await import(`i18n/meta/${locale}.json`);
11+
12+
return {
13+
title: {
14+
absolute: dictionary.tickets.title,
15+
template: `%s - API Platform Conference 2026`,
16+
},
17+
description: dictionary.tickets.description,
18+
openGraph: {
19+
title: `${dictionary.tickets.title} - API Platform Conference`,
20+
description: dictionary.tickets.description,
21+
},
22+
twitter: {
23+
title: `${dictionary.tickets.title} - API Platform Conference`,
24+
description: dictionary.tickets.description,
25+
},
26+
alternates: {
27+
languages: {
28+
en: locale === "en" ? undefined : "/con/2026/tickets",
29+
fr: locale === "fr" ? undefined : "/fr/con/2026/tickets",
30+
},
31+
},
32+
};
33+
}
34+
35+
export default async function Page({ params }: { params: { locale: Locale } }) {
36+
return <RegisterPage />;
37+
}

pwa/components/con/common/BuyButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function BuyButton({ children, id, className, ...props }: BuyButtonProps) {
1414
return (
1515
<Button
1616
id={id}
17-
to={`/${locale}/con/2025/tickets`}
17+
to={`/${locale}/con/2026/tickets`}
1818
className={classNames("pink flex flex-row gap-1", className)}
1919
{...props}
2020
>

pwa/data/con/2026/nav.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const nav = {
1414
to: "/{{locale}}/con/2025/review",
1515
text: "footer.previous_edition.links.review",
1616
},
17+
{
18+
to: "/{{locale}}/con/2026/#pricing",
19+
text: "nav.links.pricing",
20+
},
1721
{
1822
to: "/{{locale}}/con/2026/#sponsorship",
1923
text: "nav.links.sponsorship",

0 commit comments

Comments
 (0)