-
-
Notifications
You must be signed in to change notification settings - Fork 205
feat: added download agenda as a pdf #823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
3d3378a
80829e3
1e85eb3
1dfd8f2
1920996
9b57118
5b8b1fc
885c85c
4f40261
7e2c441
14223e6
8b2b163
fed790c
effd067
e61e26b
569a750
0f841c7
e6e0cbf
f45419d
0e02fa8
7413795
f323d10
da767c2
d81692a
9f6e7f5
b93f9e0
ce4c1af
cd9a72d
6d50cd0
83e52ef
e4f4cbe
c9b11ae
774126d
cdcc4e8
066b7f7
c0a0b9f
2cd86b5
040cbd2
72fcdb3
10b3b60
0318131
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| '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'; | ||
|
|
||
| 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: any[]) => { | ||
| 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: any }) => { | ||
| 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: any, j: React.Key | null | undefined) => { | ||
|
||
| const sp = city.speakers.find((s: any) => s.id === id); | ||
| return ( | ||
| <View key={j}> | ||
| <Text style={styles.speakerName}>{sp?.name}</Text> | ||
| <Text style={styles.speakerTitle}>{sp?.title}</Text> | ||
| </View> | ||
| ); | ||
| }) | ||
| : (() => { | ||
| const sp = city.speakers.find( | ||
| (s: any) => 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: any }) => ( | ||
|
||
| <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: any }) => ( | ||
|
||
| <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 md:w-[200px] px-10 py-3" | ||
| disabled={loading} | ||
| > | ||
| {loading ? 'Preparing PDF…' : 'Download PDF'} | ||
|
||
| </Button> | ||
| )} | ||
| </PDFDownloadLink> | ||
| </div> | ||
| ); | ||
|
|
||
| export default AgendaPDF; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The speaker ID can't be a type of any. It has to be a type of number