Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/images/press/andresospina-pycon-2026.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/press/enter-pycon-2026.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/press/guapacho-pycon-2026.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/daniel-galvis.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/daniel-sabogal.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/esneider-bravo-benitez.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/felipe-sanchez.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/felix-mino.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/hazel-saenz.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/isabel-mora-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/isabel-mora.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/jeronimo-lopez-gomez.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/johnny-montoya.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/speakers/juan-guillermo-gomez.jpg
Binary file added public/images/speakers/marcela-hoyos-cano.jpg
Binary file added public/images/speakers/maris-botero.png
Binary file added public/images/speakers/nicolas-roldan-fajardo.jpg
Binary file added public/images/speakers/raul-rodriguez.png
Binary file added public/images/speakers/roberto-bedoya-garcia.jpg
Binary file added public/images/speakers/sankalp-gilda.jpg
50 changes: 50 additions & 0 deletions public/images/sponsors/enter-co.svg
750 changes: 750 additions & 0 deletions public/images/sponsors/guapacho.svg

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions src/app/(pages)/keynotes/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import CTASection from "@/components/blocks/cta/cta";
import KeynoteDetail from "@/components/blocks/keynotes/keynote-detail";
import SectionSeparator from "@/components/section-separator";
import {
buildKeynoteJsonLd,
buildKeynotePageMetadata,
getKeynoteShareImageUrl,
} from "@/lib/keynote-seo";
import { getAllKeynoteSlugs, getLocalizedKeynote } from "@/lib/keynotes";
import { STATIC_PRERENDER_LOCALE } from "@/lib/site-locale-constants";
import { siteMessages } from "@/lib/site-messages";
import { getSiteUrl, webPageJsonLd, websiteJsonLd } from "@/lib/site-seo";

export async function generateStaticParams() {
return getAllKeynoteSlugs().map((slug) => ({ slug }));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;

return buildKeynotePageMetadata(slug, STATIC_PRERENDER_LOCALE);
}

export const dynamicParams = false;

const KeynoteSlugPage = async ({
params,
}: {
params: Promise<{ slug: string }>;
}) => {
const { slug } = await params;

const keynote = getLocalizedKeynote(slug, STATIC_PRERENDER_LOCALE);

if (!keynote) {
notFound();
}

const messages = siteMessages[STATIC_PRERENDER_LOCALE];
const keynoteUrl = `${getSiteUrl()}/keynotes/${slug}`;
const shareImageUrl = getKeynoteShareImageUrl(slug);

const jsonLd = {
"@context": "https://schema.org",
"@graph": [
websiteJsonLd(),
webPageJsonLd({
name: `${keynote.name} — PyCon Colombia 2026 Keynote`,
description: keynote.description,
url: keynoteUrl,
image: shareImageUrl,
}),
buildKeynoteJsonLd(keynote, STATIC_PRERENDER_LOCALE, keynoteUrl),
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: messages.nav.home,
item: `${getSiteUrl()}/`,
},
{
"@type": "ListItem",
position: 2,
name: messages.nav.keynotes,
item: `${getSiteUrl()}/#benefits`,
},
{
"@type": "ListItem",
position: 3,
name: keynote.name,
item: keynoteUrl,
},
],
},
],
};

return (
<>
<KeynoteDetail slug={slug} />

<SectionSeparator />

<CTASection />

<script
type="application/ld+json"
/* biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD is static structured data */
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
}}
/>
</>
);
};

export default KeynoteSlugPage;
25 changes: 25 additions & 0 deletions src/app/(pages)/keynotes/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Metadata } from "next";

import Keynotes from "@/components/blocks/keynotes/keynotes";
import { STATIC_PRERENDER_LOCALE } from "@/lib/site-locale-constants";
import { siteMessages } from "@/lib/site-messages";
import { getSiteUrl, SITE_KEYWORDS } from "@/lib/site-seo";

export async function generateMetadata(): Promise<Metadata> {
const meta = siteMessages[STATIC_PRERENDER_LOCALE].pageMeta.keynotes;

return {
title: meta.title,
description: meta.description,
keywords: [...SITE_KEYWORDS, "PyCon keynotes", "keynote speakers"],
alternates: {
canonical: `${getSiteUrl()}/keynotes`,
},
};
}

const KeynotesPage = () => {
return <Keynotes />;
};

export default KeynotesPage;
4 changes: 4 additions & 0 deletions src/app/(pages)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const navigationData: Navigation[] = [
titleKey: "sponsors",
href: "/#sponsors",
},
{
titleKey: "press",
href: "/press",
},
{
titleKey: "team",
href: "/team",
Expand Down
39 changes: 39 additions & 0 deletions src/app/(pages)/press/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Metadata } from "next";

import CTASection from "@/components/blocks/cta/cta";
import PressCoverage from "@/components/blocks/press/press-coverage";
import SectionSeparator from "@/components/section-separator";
import { getPressCoverageItems } from "@/lib/press-coverage";
import { buildPressPageJsonLd, buildPressPageMetadata } from "@/lib/press-seo";
import { STATIC_PRERENDER_LOCALE } from "@/lib/site-locale-constants";

export async function generateMetadata(): Promise<Metadata> {
const items = getPressCoverageItems();

return buildPressPageMetadata(STATIC_PRERENDER_LOCALE, items);
}

const PressPage = () => {
const items = getPressCoverageItems();
const jsonLd = buildPressPageJsonLd(STATIC_PRERENDER_LOCALE, items);

return (
<>
<PressCoverage items={items} />

<SectionSeparator />

<CTASection />

<script
type="application/ld+json"
/* biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD is static structured data */
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
}}
/>
</>
);
};

export default PressPage;
43 changes: 43 additions & 0 deletions src/app/(pages)/talks/format/[format]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";

import Talks from "@/components/blocks/talks/talks";
import { STATIC_PRERENDER_LOCALE } from "@/lib/site-locale-constants";
import { getAllTalkFormatSlugs, isTalkFormat } from "@/lib/talk-formats";
import { buildTalkFormatPageMetadata } from "@/lib/talk-seo";

export async function generateStaticParams() {
return getAllTalkFormatSlugs().map((format) => ({ format }));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ format: string }>;
}): Promise<Metadata> {
const { format } = await params;

if (!isTalkFormat(format)) {
return {};
}

return buildTalkFormatPageMetadata(format, STATIC_PRERENDER_LOCALE);
}

export const dynamicParams = false;

const TalkFormatPage = async ({
params,
}: {
params: Promise<{ format: string }>;
}) => {
const { format } = await params;

if (!isTalkFormat(format)) {
notFound();
}

return <Talks activeFormat={format} />;
};

export default TalkFormatPage;
6 changes: 6 additions & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { MetadataRoute } from "next";

import { getAllKeynoteSlugs } from "@/lib/keynotes";
import { getPosts } from "@/lib/posts";
import { getSiteUrl } from "@/lib/site-seo";
import { getAllSpeakerTrackSlugs } from "@/lib/speaker-tracks";
import { getAllSpeakerSlugs } from "@/lib/speakers";
import { getAllSponsorSlugs } from "@/lib/sponsors";
import { getAllTalkFormatSlugs } from "@/lib/talk-formats";
import { getAllTalkLanguageSlugs } from "@/lib/talk-languages";
import { getAllTalkLevelSlugs } from "@/lib/talk-levels";
import { getAllTalkIds, getTalkHref } from "@/lib/talks";
Expand All @@ -17,12 +19,16 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const routes = [
"" /* This is equivalent to / */,
"/blog",
"/press",
"/pricing",
"/team",
"/speakers",
"/keynotes",
...getAllKeynoteSlugs().map((slug) => `/keynotes/${slug}`),
"/talks",
...getAllSpeakerTrackSlugs().map((slug) => `/talks/${slug}`),
...getAllTalkLevelSlugs().map((level) => `/talks/level/${level}`),
...getAllTalkFormatSlugs().map((format) => `/talks/format/${format}`),
...getAllTalkLanguageSlugs().map(
(language) => `/talks/language/${language}`,
),
Expand Down
37 changes: 21 additions & 16 deletions src/assets/data/benefits.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import keynotesEs from "@/assets/data/keynotes.es.json";
import keynotesEn from "@/assets/data/keynotes.json";
import { keynoteList } from "@/assets/data/keynotes";
import { keynoteContentByLocale } from "@/assets/data/keynotes-content.locale";
import type { Features } from "@/components/blocks/benefits/benefits";
import type { SiteLocale } from "@/lib/site-messages";

export function getBenefitsFeatures(locale: SiteLocale): Features {
const keynotes = locale === "es" ? keynotesEs : keynotesEn;
const content = keynoteContentByLocale[locale];

return keynotes.map((keynote) => ({
title: keynote.name,
role: keynote.role,
country: keynote.country,
description: keynote.description,
image: keynote.image,
flag: keynote.flag,
linkedin: keynote.linkedin,
github: keynote.github,
website: keynote.website,
youtube: keynote.youtube,
x: keynote.x,
}));
return keynoteList.map((keynote) => {
const localized = content[keynote.slug];

return {
slug: keynote.slug,
title: localized.name,
role: localized.role,
country: localized.country,
description: localized.description,
image: keynote.image,
flag: keynote.flag,
linkedin: keynote.linkedin,
github: keynote.github,
website: keynote.website,
youtube: keynote.youtube,
x: keynote.x,
};
});
}
Loading
Loading