Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3d3378a
added download pdf component
harshhgithub Oct 22, 2025
80829e3
Merge branch 'master' into agenda-as-pdf
harshhgithub Oct 22, 2025
1e85eb3
resolveed conflicts
harshhgithub Oct 22, 2025
1dfd8f2
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Oct 22, 2025
1920996
resolved
harshhgithub Oct 22, 2025
9b57118
removed the comments and fixed the wording
harshhgithub Oct 25, 2025
5b8b1fc
Merge branch 'master' into agenda-as-pdf
harshhgithub Nov 15, 2025
885c85c
Merge branch 'master' into agenda-as-pdf
AceTheCreator Nov 15, 2025
4f40261
Merge branch 'master' into agenda-as-pdf
harshhgithub Nov 15, 2025
7e2c441
made the requested changes
harshhgithub Nov 15, 2025
14223e6
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Nov 15, 2025
8b2b163
Merge branch 'master' into agenda-as-pdf
thulieblack Dec 2, 2025
fed790c
fixed
harshhgithub Dec 2, 2025
effd067
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Dec 2, 2025
e61e26b
Merge branch 'master' into agenda-as-pdf
AceTheCreator Dec 3, 2025
569a750
Merge branch 'master' into agenda-as-pdf
harshhgithub Dec 6, 2025
0f841c7
Merge branch 'master' into agenda-as-pdf
harshhgithub Dec 11, 2025
e6e0cbf
Merge branch 'master' into agenda-as-pdf
AceTheCreator Dec 11, 2025
f45419d
Merge branch 'master' into agenda-as-pdf
harshhgithub Dec 12, 2025
0e02fa8
made the changes
harshhgithub Dec 12, 2025
7413795
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Dec 12, 2025
f323d10
made some changes
harshhgithub Dec 13, 2025
da767c2
check
harshhgithub Dec 13, 2025
d81692a
dependencies
harshhgithub Dec 13, 2025
9f6e7f5
fixed
harshhgithub Dec 13, 2025
b93f9e0
fixed errors
harshhgithub Dec 13, 2025
ce4c1af
Merge branch 'master' into agenda-as-pdf
AceTheCreator Dec 17, 2025
cd9a72d
Merge branch 'master' into agenda-as-pdf
harshhgithub Dec 20, 2025
6d50cd0
extended getEventStatusMethod
harshhgithub Jan 24, 2026
83e52ef
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Jan 24, 2026
e4f4cbe
Merge branch 'master' into agenda-as-pdf
harshhgithub Jan 24, 2026
c9b11ae
made the asked changes and added unit test
harshhgithub Jan 28, 2026
774126d
Merge branch 'agenda-as-pdf' of https://github.com/harshhgithub/confe…
harshhgithub Jan 28, 2026
cdcc4e8
Merge branch 'master' into agenda-as-pdf
harshhgithub Feb 12, 2026
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
204 changes: 204 additions & 0 deletions components/Agenda/DownloadAgenda.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
'use client';
import React from 'react';
import {
Page,
Text,
View,
Document,
StyleSheet,
Image,
Font,
PDFDownloadLink,
PDFViewer,
} from '@react-pdf/renderer';
import Button from '../Buttons/button';
import { ExtendedCity, Speaker, Agenda as AgendaType } from '../../types/types';

Font.register({
family: 'Open Sans',
fonts: [
{
src: 'https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-regular.ttf',
},
{
src: 'https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-600.ttf',
fontWeight: 600,
},
{
src: 'https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-800.ttf',
fontWeight: 800,
},
],
});

const styles = StyleSheet.create({
page: {
backgroundColor: '#1B1130',
padding: 40,
color: '#fff',
fontFamily: 'Open Sans',
},
logo: {
width: 90,
marginBottom: 10,
alignSelf: 'center',
},
header: {
textAlign: 'center',
marginBottom: 20,
},
title: {
fontSize: 26,
fontWeight: 800,
marginBottom: 4,
},
subtitle: {
fontSize: 16,
fontWeight: 600,
color: '#E74694',
},
date: {
textAlign: 'center',
fontSize: 13,
marginTop: 6,
},
tableHeader: {
flexDirection: 'row',
borderBottomWidth: 1,
borderBottomColor: '#E74694',
paddingBottom: 6,
marginTop: 12,
},
headerText: {
fontSize: 12,
fontWeight: 600,
color: '#E74694',
},
row: {
flexDirection: 'row',
borderBottomWidth: 0.5,
borderBottomColor: '#666',
paddingVertical: 8,
},

timeCol: { width: '17%', fontSize: 12, paddingRight: 6 },
sessionCol: { width: '50%', paddingHorizontal: 10 },
speakerCol: { width: '33%', fontSize: 12, paddingLeft: 6 },
sessionTitle: { fontSize: 13, fontWeight: 600, color: '#fff' },
sessionType: { fontSize: 10, color: '#bbb' },
speakerName: { fontSize: 12, fontWeight: 600, color: '#fff' },
speakerTitle: { fontSize: 10, color: '#ccc' },

footer: {
position: 'absolute',
bottom: 25,
left: 0,
right: 0,
textAlign: 'center',
fontSize: 10,
color: '#aaa',
},
});

const cleanAgenda = (agenda: AgendaType[]) => {
let tz: string | null = null;
const processed = agenda.map((item) => {
const tzMatch = item.time.match(/\b(?!AM|PM)([A-Z]{2,4})\b/g);
if (tzMatch && !tz) tz = tzMatch[0];
return { ...item, time: item.time.replace(/\b(?!AM|PM)([A-Z]{2,4})\b/g, '') };
});
return { processed, tz };
};

const AgendaPDF = ({ city }: { city: ExtendedCity }) => {
const { processed, tz } = cleanAgenda(city.agenda);
return (
<Document>
<Page style={styles.page}>
<View style={styles.header}>
<Image src="/img/logos/2025-logo.png" style={styles.logo} />
<Text style={styles.title}>
{city.name}, {city.country}
</Text>
<Text style={styles.subtitle}>Conference Agenda</Text>
<Text style={styles.date}>
{city.date} {tz && `(${tz})`}
</Text>
</View>

<View style={styles.tableHeader}>
<Text style={[styles.headerText, { width: '17%' }]}>Time</Text>
<Text style={[styles.headerText, { width: '50%' }]}>Session</Text>
<Text style={[styles.headerText, { width: '33%' }]}>Speaker</Text>
</View>

{processed.map((item, i) => (
<View style={styles.row} key={i} wrap={false}>
<Text style={styles.timeCol}>{item.time}</Text>
<View style={styles.sessionCol}>
<Text style={styles.sessionTitle}>{item.session}</Text>
<Text style={styles.sessionType}>{item.type}</Text>
</View>
<View style={styles.speakerCol}>
{Array.isArray(item.speaker)
? item.speaker.map((id: number) => {
const sp = city.speakers.find((s: Speaker) => s.id === id);
return (
<View key={id}>
<Text style={styles.speakerName}>{sp?.name}</Text>
<Text style={styles.speakerTitle}>{sp?.title}</Text>
</View>
);
})
: (() => {
const sp = city.speakers.find(
(s: Speaker) => s.id === item.speaker
);
return (
<>
<Text style={styles.speakerName}>{sp?.name}</Text>
<Text style={styles.speakerTitle}>{sp?.title}</Text>
</>
);
})()}
</View>
</View>
))}

<Text style={styles.footer}>
AsyncAPI Conference © {new Date().getFullYear()}
</Text>
</Page>
</Document>
);
};

export const PdfViewer = ({ city }: { city: ExtendedCity }) => (
<div className="w-full flex justify-center h-full">
<PDFViewer className="w-[85%] h-[90vh]">
<AgendaPDF city={city} />
</PDFViewer>
</div>
);

export const PdfDownloadButton = ({ city }: { city: ExtendedCity }) => (
<div className="w-full flex justify-center mt-6">
<PDFDownloadLink
document={<AgendaPDF city={city} />}
fileName={`${city.name}-Agenda.pdf`}
style={{ textDecoration: 'none', width: '100%', maxWidth: '220px' }}
>
{({ loading }) => (
<Button
type="button"
className="w-full min-w-[200px] px-10 py-3 whitespace-nowrap"
disabled={loading}
>
{loading ? 'Preparing Agenda…' : 'Download Agenda'}
</Button>
)}
</PDFDownloadLink>
</div>
);

export default AgendaPDF;
32 changes: 25 additions & 7 deletions components/Agenda/agenda.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import React, { JSX } from 'react';
import React ,{JSX}from 'react';
import Heading from '../Typography/heading';
import Paragraph from '../Typography/paragraph';
import { Agenda as AgendaType, ExtendedCity } from '../../types/types';
import { Agenda as AgendaType, ExtendedCity, Speaker } from '../../types/types';
import Image from 'next/image';
import { PdfDownloadButton } from './DownloadAgenda';

interface IAgenda {
city: ExtendedCity;
}

const isPastEvent = (dateString: string): boolean => {
if (dateString.includes('-')) {
const parts = dateString.split('-').map(p => p.trim());
const endDateString = parts[parts.length - 1];
const endDate = new Date(endDateString);
return endDate < new Date();
}
return new Date(dateString) < new Date();
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add a helper function to check whether an event is in the past, so it can be reused throughout the application. Right now, a new check is created each time it’s needed (for example, the tickets section also checks for past events).

function Agenda({ city }: IAgenda): JSX.Element {
return (
<div className="" data-test="agenda-com">
Expand All @@ -33,12 +44,13 @@ function Agenda({ city }: IAgenda): JSX.Element {

<div className="mt-[40px]">
{city.agenda.map((talk: AgendaType) => {
const getSpeaker = city.speakers.filter((speaker) => {
if (typeof talk.speaker === 'object') {
const getSpeaker = city.speakers.filter((speaker: Speaker) => {
if (Array.isArray(talk.speaker)) {
return talk.speaker.includes(speaker.id);
}
return speaker.id === talk.speaker;
});

return (
<div
key={talk.time}
Expand All @@ -58,6 +70,7 @@ function Agenda({ city }: IAgenda): JSX.Element {
{talk.session}
</Heading>
</div>

{talk.speaker && typeof talk.speaker === 'number' ? (
<div className="flex items-center lg:mt-4">
<div className="w-[94px] h-[94px]">
Expand All @@ -84,12 +97,13 @@ function Agenda({ city }: IAgenda): JSX.Element {
) : (
<div></div>
)}
{talk.speaker && typeof talk.speaker === 'object' && (

{talk.speaker && Array.isArray(talk.speaker) && (
<div className="flex flex-col">
{getSpeaker.length > 1 &&
getSpeaker.map((speaker, i) => {
getSpeaker.map((speaker: Speaker) => {
return (
<div key={i} className="mt-6">
<div key={speaker.id} className="mt-6">
<div className="flex items-center lg:mt-4">
<div className="w-[94px] h-[94px]">
<Image
Expand Down Expand Up @@ -128,6 +142,10 @@ function Agenda({ city }: IAgenda): JSX.Element {
</div>
)}
</div>

<div className="mt-[60px]">
{!isPastEvent(city.date) && <PdfDownloadButton city={city} />}
</div>
</div>
);
}
Expand Down
Loading