Skip to content

Commit 50c580f

Browse files
authored
Add speakers page (#197)
* Intialize Speaker page * Add speaker page * Modified js and tsx file to jsx * Fix speaker page bio header alignment * Improve accessibility * Speakers page enhancements * Add speaker modal and speakers data added in schedule * Modify schedule based on the data modification * Remove speakers data * Remove speakers import statement * Talk Deatils section added in speakers infor page
1 parent d27b630 commit 50c580f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1390
-470
lines changed

components/customModal.jsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Modal } from "react-bootstrap";
2+
3+
const CustomModal = ({ showModal, handleClose, children }) => {
4+
return (
5+
<Modal show={showModal} size="lg">
6+
<Modal.Body>
7+
<div className="row justify-content-end">
8+
<button
9+
type="button"
10+
className="btn-close"
11+
aria-label="Close"
12+
onClick={handleClose}
13+
></button>
14+
</div>
15+
{children}
16+
</Modal.Body>
17+
</Modal>
18+
);
19+
};
20+
21+
export default CustomModal;

components/header.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ const navBarItems = [
3232
},
3333
],
3434
},
35-
{
36-
name: "Keynotes",
37-
href: "/#keynote",
38-
id: "keynote",
39-
openInNewTab: false,
40-
},
4135
{
4236
name: "Schedule",
4337
href: "/#schedule",
@@ -56,6 +50,12 @@ const navBarItems = [
5650
id: "community-partners",
5751
openInNewTab: false,
5852
},
53+
{
54+
name: "Speakers",
55+
href: "/speakers",
56+
id: "speakers",
57+
openInNewTab: false,
58+
},
5959
{
6060
name: "Blog",
6161
href: "https://in.pycon.org/blog/",

components/icons.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { FaBars, FaBriefcase, FaBuilding, FaCircleArrowRight, FaEnvelope, FaFacebook, FaGithub, FaLocationDot, FaGlobe, FaInstagram, FaLinkedin, FaMastodon, FaTwitter } from "react-icons/fa6";
1+
import { FaWindowClose } from "react-icons/fa";
2+
import { FaBars, FaBriefcase, FaBuilding, FaArrowUpRightFromSquare, FaMaximize, FaCircleArrowRight, FaEnvelope, FaFacebook, FaGithub, FaLocationDot, FaGlobe, FaInstagram, FaLinkedin, FaMastodon, FaTwitter, FaYoutube, FaMedium } from "react-icons/fa6";
23
import { SiZulip } from "react-icons/si";
34

4-
55
export const icons = {
66
arrowRight: FaCircleArrowRight,
77
bars: FaBars,
@@ -12,11 +12,17 @@ export const icons = {
1212
facebook: FaFacebook,
1313
github: FaGithub,
1414
instagram: FaInstagram,
15+
medium: FaMedium,
1516
linkedin: FaLinkedin,
1617
mastodon: FaMastodon,
1718
twitter: FaTwitter,
1819
website: FaGlobe,
19-
zulip: SiZulip
20+
youtube: FaYoutube,
21+
close: FaWindowClose,
22+
maximizeWindow: FaMaximize,
23+
newTab: FaArrowUpRightFromSquare,
24+
zulip: SiZulip,
25+
2026
};
2127

2228
export const IconComponent = ({ ...props }) => {

components/keynote.js

Lines changed: 0 additions & 60 deletions
This file was deleted.

components/nameAvatar.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
function getRandomColor() {
2+
// Function to generate a random color
3+
const letters = '0123456789ABCDEF';
4+
let color = '#';
5+
for (let i = 0; i < 6; i++) {
6+
color += letters[Math.floor(Math.random() * 16)];
7+
}
8+
return color;
9+
}
10+
11+
12+
function getContrastColor(backgroundColor) {
13+
// Function to get a contrasting text color based on the background color
14+
const hex = backgroundColor.slice(1);
15+
const r = parseInt(hex.slice(0, 2), 16);
16+
const g = parseInt(hex.slice(2, 4), 16);
17+
const b = parseInt(hex.slice(4, 6), 16);
18+
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
19+
// Use black or white text based on brightness
20+
return brightness > 128 ? '#000000' : '#ffffff';
21+
}
22+
23+
const NameAvatar = (props) => {
24+
const initials = props.name.split(' ').map(word => word[0]).join('').toUpperCase();
25+
const backgroundColor = getRandomColor();
26+
const textColor = getContrastColor(backgroundColor);
27+
28+
return (
29+
<div
30+
className={`name-initial-avatar ${props.className}`}
31+
style={{
32+
fontSize: 50,
33+
color: textColor,
34+
backgroundColor: backgroundColor
35+
}}
36+
>
37+
{initials}
38+
</div>)
39+
};
40+
41+
export default NameAvatar;

components/schedule.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Accordion, Card } from "react-bootstrap";
22
import ScheduleData from "../data/schedule.yml";
33
import React, { useState } from "react";
4+
import Link from "next/link";
45

56
// group by array using a condition, param 1 -> array, param 2 -> function for filtering and grouping
67
const groupBy = (a, f) => a.reduce((x, c) => (x[f(c)] ??= []).push(c) && x, {});
@@ -39,9 +40,7 @@ const ConferenceSchedule = () => {
3940
{ScheduleData.map((item, index) => (
4041
<li className="nav-item" role="presentation" key={index}>
4142
<button
42-
className={`nav-link ${
43-
index === selectedTab ? "active" : "nav-link-inactive"
44-
}`}
43+
className={`nav-link ${index === selectedTab ? "active" : "nav-link-inactive"}`}
4544
id={`pills-tab-${index}`}
4645
data-bs-toggle="pill"
4746
data-bs-target={`#pills-${index}`}
@@ -67,7 +66,7 @@ const ConferenceSchedule = () => {
6766
>
6867
<div className="row">
6968
{currentSchedule.schedule.map((scheduleItem, idx) => {
70-
const { time, image, description, speaker, track } =
69+
const { time, image, topic, speakers, track } =
7170
scheduleItem;
7271
return <ScheduleCard {...scheduleItem} key={idx} />;
7372
})}
@@ -122,18 +121,30 @@ function ScheduleCard({ image, time, talks }) {
122121
);
123122
}
124123

125-
function ScheduleTalk({ description, speaker, track, size, proposalLink }) {
124+
function ScheduleTalk({ title, speakers, track, size, proposalLink }) {
126125
return (
127126
<>
128127
<div className={`col-${size} ${size == 1 || "text-center"}`}>
129128
<p className="mb-0 date-content">
130-
<a href={proposalLink} target="_blank" rel="noopener noreferrer">
131-
{description}
132-
</a>
133-
{speaker && <span className="ft-weight"> <br /> {speaker} </span>}
129+
{proposalLink ?
130+
<Link
131+
href={proposalLink}
132+
target="_blank"
133+
style={{ textDecoration: "none" }}
134+
>
135+
{title}
136+
</Link> :
137+
<span>{title}</span>
138+
}
139+
{speakers && speakers.map(speaker => (
140+
<span key={speaker.id} className="ft-weight" >
141+
<br />
142+
{speaker.fullName}
143+
</span>
144+
))}
134145
{/* {track && <span className="rt-green text-white">{track}</span>} */}
135146
</p>
136-
</div>
147+
</div >
137148
</>
138149
);
139150
}
@@ -143,20 +154,27 @@ function ScheduleAccordion({ date, currentSchedule, id, handleTabClick }) {
143154
<Accordion key={id} className="d-block d-lg-none">
144155
<Accordion.Item eventKey={id} onClick={() => handleTabClick(id)}>
145156
<Accordion.Header>{date}</Accordion.Header>
146-
<Accordion.Body style={{padding: "1rem 0rem"}}>
157+
<Accordion.Body style={{ padding: "1rem 0rem" }}>
147158
{currentSchedule.schedule.map((scheduleItem, idx) => {
148159
return scheduleItem.talks.map(talk => {
149160
return (
150161
<Card style={{ margin: '0.8rem 0' }} key={idx}>
151162
<Card.Body>
152163
<Card.Title>
153164
<a href={talk.proposalLink} target="_blank" rel="noopener noreferrer">
154-
{talk.description}
165+
{talk.title}
155166
</a>
156167
<br />
157-
{talk.speaker && <>
158-
<span> By {talk.speaker} </span>
159-
</>}
168+
{talk.speakersPlaceHolder ?
169+
<span>By {talk.speakersPlaceHolder}</span> :
170+
talk.speakers && <span>{"By "}</span>
171+
}
172+
{talk.speakers && talk.speakers.map((speaker, index) => (
173+
<span key={speaker.id} className="ft-weight">
174+
{speaker.fullName}
175+
{index !== 0 && " & "}
176+
</span>
177+
))}
160178
</Card.Title>
161179
<Card.Subtitle className="mb-2 text-muted">{scheduleItem.time}</Card.Subtitle>
162180
{/* <Card.Text>
@@ -176,7 +194,7 @@ function ScheduleAccordion({ date, currentSchedule, id, handleTabClick }) {
176194
// </div>
177195

178196
// <div className="col-md-7 col-5" key={idx}>
179-
// <p className="mb-0 date-content">{talk.description}</p>
197+
// <p className="mb-0 date-content">{talk.title}</p>
180198
// </div>
181199
// </div>
182200
);

components/speakerDetail.jsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import Image from "next/image";
2+
import Link from "next/link";
3+
4+
import IconComponent from "components/icons";
5+
import NameAvatar from "components/nameAvatar";
6+
7+
const TalkDetail = (speaker) => {
8+
return (
9+
<>
10+
<span>Talk Details:</span>
11+
<ul className="ms-3">
12+
{speaker.proposalLink ?
13+
<li>Addressing: <Link target="_blank" href={speaker.proposalLink}>{speaker.proposalTitle}</Link></li> :
14+
<li>Addressing: {speaker.proposalTitle}</li>
15+
}
16+
{speaker.talkDate && <li>Date: {speaker.talkDate}</li>}
17+
{speaker.talkTime && <li>Time: {speaker.talkTime}</li>}
18+
{speaker.track && <li>Track: {speaker.track}</li>}
19+
20+
</ul>
21+
</>
22+
)
23+
}
24+
25+
const SpeakerDetail = ({ speaker, showHyperLink }) => {
26+
return (
27+
<div>
28+
<div className="container bg-speaker-bio-box">
29+
<div className="row bg-speaker-bio-header w-100">
30+
<div className="d-flex col-12 py-4 justify-content-center">
31+
{speaker.profilePicture ? (
32+
<Image
33+
src={speaker.profilePicture}
34+
alt={`Image of ${speaker.fullName}`}
35+
className="speaker-bio-image"
36+
width={400}
37+
height={400}
38+
priority
39+
/>
40+
) : (
41+
<NameAvatar
42+
className="speaker-bio-image"
43+
name={speaker.fullName}
44+
/>
45+
)}
46+
</div>
47+
<div className="col-12 py-2 text-center">
48+
<h1>{speaker.fullName}</h1>
49+
<p>{speaker.title}</p>
50+
</div>
51+
</div>
52+
<div className="row bg-speaker-bio-about pt-4 px-4">
53+
<p dangerouslySetInnerHTML={{ __html: speaker.about }}></p>
54+
{speaker.proposalTitle &&
55+
<TalkDetail {...speaker} />}
56+
</div>
57+
<div className="d-flex justify-content-between bg-speaker-bio-social py-2 px-4">
58+
<div>
59+
{speaker.social.map((item, index) => (
60+
<span className="me-1" key={index}>
61+
<Link
62+
href={item.link}
63+
target="_blank"
64+
aria-label={`Hyperlink to speaker's ${item.icon} profile.`}
65+
>
66+
<IconComponent
67+
className="my-1"
68+
name={item.platform}
69+
color="#fff"
70+
backgroundColor="1f928d"
71+
/>
72+
</Link>
73+
</span>
74+
))}
75+
</div>
76+
{showHyperLink && (
77+
<div>
78+
<span className="me-1 d-flex flex-end">
79+
<Link
80+
href="/speakers/[id]"
81+
as={`/speakers/${encodeURIComponent(
82+
speaker.id.toLowerCase()
83+
)}`}
84+
style={{ textDecoration: "none" }}
85+
target="_blank"
86+
>
87+
<IconComponent
88+
className="my-1"
89+
name="newTab"
90+
color="#fff"
91+
backgroundColor="1f928d"
92+
/>
93+
</Link>
94+
</span>
95+
</div>
96+
)}
97+
</div>
98+
</div>
99+
</div>
100+
);
101+
};
102+
103+
export default SpeakerDetail;

0 commit comments

Comments
 (0)