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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
"clsx": "2.1.1",
"embla-carousel-react": "^8.6.0",
"gray-matter": "4.0.3",
"jspdf": "4.2.1",
"lucide-react": "0.562.0",
"motion": "12.29.2",
"next": "16.1.1",
"next-mdx-remote-client": "2.1.7",
"next-themes": "0.4.6",
"qrcode": "1.5.4",
"radix-ui": "1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
Expand All @@ -30,6 +32,7 @@
"@biomejs/biome": "2.4.15",
"@tailwindcss/postcss": "4.1.18",
"@types/node": "22.19.3",
"@types/qrcode": "1.5.6",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"prettier": "3.7.4",
Expand Down
396 changes: 396 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
allowBuilds:
core-js: set this to true or false
sharp: true
Binary file added public/images/avatar/alex-correa.jpeg
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/avatar/carolina-patino.jpeg
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/avatar/federico-cardozo.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/avatar/isabela-mejia-arena.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/avatar/isabella-pardo-tabares.jpeg
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/avatar/julian-agudelo.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/avatar/kevin-rueda.jpeg
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/avatar/maria-alejandra-ocampo.jpeg
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/avatar/mariana-gutierrez.jpeg
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/avatar/mateo-amaya.jpeg
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/avatar/oscar-jaime.jpeg
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/avatar/santiago-gomez.jpeg
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/avatar/santiago-molano.jpeg
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/avatar/sebastian-fernandez.jpeg
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/avatar/yeimy-bedoya.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions src/app/(pages)/certificates/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";

import CertificateDetail from "@/components/blocks/certificates/certificate-detail";
import CTASection from "@/components/blocks/cta/cta";
import SectionSeparator from "@/components/section-separator";
import {
getAllCertificateIds,
getCertificateHref,
getCertificateWithUser,
} from "@/lib/certificates";
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 getAllCertificateIds().map((id) => ({ id }));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
const certificate = getCertificateWithUser(id);
const meta = siteMessages[STATIC_PRERENDER_LOCALE].pageMeta.certificates;

if (!certificate) {
return {
title: meta.title,
description: meta.description,
};
}

const title = meta.detailTitle.replace("{name}", certificate.user.name);
const description = meta.detailDescription.replace(
"{name}",
certificate.user.name,
);

return {
title,
description,
alternates: {
canonical: `${getSiteUrl()}${getCertificateHref(id)}`,
},
robots: {
index: false,
follow: false,
},
};
}

export const dynamicParams = false;

const CertificatePage = async ({
params,
}: {
params: Promise<{ id: string }>;
}) => {
const { id } = await params;
const certificate = getCertificateWithUser(id);

if (!certificate) {
notFound();
}

const messages = siteMessages[STATIC_PRERENDER_LOCALE];
const pageUrl = `${getSiteUrl()}${getCertificateHref(id)}`;
const title = messages.pageMeta.certificates.detailTitle.replace(
"{name}",
certificate.user.name,
);
const description = messages.pageMeta.certificates.detailDescription.replace(
"{name}",
certificate.user.name,
);

const jsonLd = {
"@context": "https://schema.org",
"@graph": [
websiteJsonLd(),
webPageJsonLd({
name: title,
description,
url: pageUrl,
}),
],
};

return (
<>
<CertificateDetail certificate={certificate} />

<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 CertificatePage;
57 changes: 57 additions & 0 deletions src/assets/data/certificates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Attendance certificates. Each entry has a unique public hash `id` used in
* `/certificates/[id]`. Only users listed here are considered attendees who
* receive a certificate.
*/
export type CertificateRole =
| "attendee"
| "volunteer"
| "speaker"
| "organizer";

export type Certificate = {
/** Public unique hash used in the certificate URL. */
id: string;
userId: string;
role: CertificateRole;
issuedAt: string;
};

export const certificates: Certificate[] = [
{
id: "b2e9f1a84c7d5036e91a",
userId: "usr_john_roa",
role: "organizer",
issuedAt: "2026-07-26",
},
{
id: "7c4a8d9e2f1b6a308c4e",
userId: "usr_alejandro_rendon",
role: "organizer",
issuedAt: "2026-07-26",
},
{
id: "3f8a1c6e9b2d7045a1f2",
userId: "usr_carlos_sierra",
role: "organizer",
issuedAt: "2026-07-26",
},
{
id: "9d2e7a4f1c8b6035d7e1",
userId: "usr_karen_romo",
role: "organizer",
issuedAt: "2026-07-26",
},
{
id: "5a1c8e3f7b2d9046c3a8",
userId: "usr_maria_franco",
role: "volunteer",
issuedAt: "2026-07-26",
},
{
id: "e4f7b2c9a1d68305f8e2",
userId: "usr_test_attendee",
role: "attendee",
issuedAt: "2026-07-26",
},
];
90 changes: 90 additions & 0 deletions src/assets/data/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ export const volunteerMembers: VolunteerMember[] = [
role: "Redes Sociales",
image: "/images/avatar/alejandro-alvarez.jpg",
},
{
slug: "alex-correa",
name: "Alex Correa",
role: "Logistics",
image: "/images/avatar/alex-correa.jpeg",
},
{
slug: "blanca-arcilia-florez-urrutia",
name: "Blanca Arcilia Flórez Urrutia",
Expand All @@ -121,6 +127,18 @@ export const volunteerMembers: VolunteerMember[] = [
role: "Logistics",
image: "/images/avatar/carolina-monsalve-delgado.jpeg",
},
{
slug: "carolina-patino",
name: "Carolina Patiño",
role: "Logistics",
image: "/images/avatar/carolina-patino.jpeg",
},
{
slug: "federico-cardozo",
name: "Federico Cardozo",
role: "Logistics",
image: "/images/avatar/federico-cardozo.jpg",
},
{
slug: "gladys-agudelo-patino",
name: "Gladys Agudelo Patiño",
Expand All @@ -133,6 +151,18 @@ export const volunteerMembers: VolunteerMember[] = [
role: "Auditorium",
image: "/images/avatar/gustavo-diaz-jaimes.jpg",
},
{
slug: "isabela-mejia-arena",
name: "Isabela Mejía Arena",
role: "Logistics",
image: "/images/avatar/isabela-mejia-arena.jpg",
},
{
slug: "isabella-pardo-tabares",
name: "Isabella Pardo Tabares",
role: "Logistics",
image: "/images/avatar/isabella-pardo-tabares.jpeg",
},
{
slug: "jenny-acuna",
name: "Jenny Acuña",
Expand Down Expand Up @@ -160,13 +190,67 @@ export const volunteerMembers: VolunteerMember[] = [
linkedin:
"https://www.linkedin.com/in/juan-camilo-vel%C3%A1squez-p%C3%A9rez/",
},
{
slug: "julian-agudelo",
name: "Julian Agudelo",
role: "Logistics",
image: "/images/avatar/julian-agudelo.jpg",
},
{
slug: "kevin-rueda",
name: "Kevin Rueda",
role: "Logistics",
image: "/images/avatar/kevin-rueda.jpeg",
},
{
slug: "maria-alejandra-ocampo",
name: "María Alejandra Ocampo",
role: "Logistics",
image: "/images/avatar/maria-alejandra-ocampo.jpeg",
},
{
slug: "mariana-gutierrez",
name: "Mariana Gutiérrez",
role: "Logistics",
image: "/images/avatar/mariana-gutierrez.jpeg",
},
{
slug: "mateo-amaya",
name: "Mateo Amaya",
role: "Logistics",
image: "/images/avatar/mateo-amaya.jpeg",
},
{
slug: "mateo-usme-valencia",
name: "Mateo Usme Valencia",
role: "Padrino Keynotes",
image: "/images/avatar/mateo-usme.png",
linkedin: "https://www.linkedin.com/in/mateousmevalencia/",
},
{
slug: "oscar-jaime",
name: "Oscar Jaime",
role: "Logistics",
image: "/images/avatar/oscar-jaime.jpeg",
},
{
slug: "santiago-gomez",
name: "Santiago Gómez",
role: "Logistics",
image: "/images/avatar/santiago-gomez.jpeg",
},
{
slug: "santiago-molano",
name: "Santiago Molano",
role: "Logistics",
image: "/images/avatar/santiago-molano.jpeg",
},
{
slug: "sebastian-fernandez",
name: "Sebastián Fernández",
role: "Logistics",
image: "/images/avatar/sebastian-fernandez.jpeg",
},
{
slug: "simon-joel-torres-castaneda",
name: "Simón Joel Torres Castañeda",
Expand All @@ -187,6 +271,12 @@ export const volunteerMembers: VolunteerMember[] = [
image: "/images/avatar/veruzka-borges.png",
linkedin: "https://www.linkedin.com/in/veruzkab/",
},
{
slug: "yeimy-bedoya",
name: "Yeimy Bedoya",
role: "Logistics",
image: "/images/avatar/yeimy-bedoya.jpeg",
},
{
slug: "yurley-sanchez-florez",
name: "Yurley Katterine Sanchez Florez",
Expand Down
50 changes: 50 additions & 0 deletions src/assets/data/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Conference users / attendees.
* Linked from certificates via `userId`. Only users with a certificate entry
* receive a unique verification page.
*/
export type User = {
id: string;
name: string;
email?: string;
/** Optional link to an existing team member slug. */
teamSlug?: string;
};

export const users: User[] = [
{
id: "usr_john_roa",
name: "John Roa",
email: "john@pycon.co",
teamSlug: "john-roa",
},
{
id: "usr_alejandro_rendon",
name: "Alejandro Rendon",
email: "alejandro@pycon.co",
teamSlug: "alejandro-rendon",
},
{
id: "usr_carlos_sierra",
name: "Carlos Sierra",
email: "carlos@pycon.co",
teamSlug: "carlos-sierra",
},
{
id: "usr_karen_romo",
name: "Karen Romo",
email: "karen@pycon.co",
teamSlug: "karen-romo",
},
{
id: "usr_maria_franco",
name: "Maria Franco",
email: "maria@pycon.co",
teamSlug: "maria-franco",
},
{
id: "usr_test_attendee",
name: "Camila Restrepo",
email: "camila.restrepo@example.com",
},
];
Loading
Loading