1- import React from 'react' ;
1+ import React , { useMemo } from 'react' ;
22import useScrollAnimation from '../hooks/useScrollAnimation' ;
33
4+ const TIMELINE_MILESTONES = [
5+ { date : new Date ( '2026-03-30' ) , label : 'March 30th, 2026' , title : 'CFP and Call for Sponsors Opens' , description : 'Start submitting your talk and workshop proposals via Sessionize.' } ,
6+ { date : new Date ( '2026-05-15T23:59:00+01:00' ) , label : 'May 15th, 2026, 11:59 PM WAT' , title : 'CFP Closes' , description : "Last day to submit your proposals. Don't wait until the last minute!" } ,
7+ { date : new Date ( '2026-05-30' ) , label : 'May 30th, 2026' , title : 'Announcement of Speakers' , description : 'Selected speakers will be announced. All submitters will receive notifications.' } ,
8+ { date : new Date ( '2026-06-15' ) , label : 'June 15th, 2026' , title : 'Schedule Published' , description : 'The full conference schedule is posted on the PyCon Cameroon website.' } ,
9+ { date : new Date ( '2026-09-19' ) , label : 'September 17th-19th, 2026' , title : 'PyCon Cameroon 2026' , description : 'The main event in Yaoundé, Cameroon!' } ,
10+ ] ;
11+
12+ function getCfpStatus ( now ) {
13+ const cfpOpen = TIMELINE_MILESTONES [ 0 ] . date ;
14+ const cfpClose = TIMELINE_MILESTONES [ 1 ] . date ;
15+ if ( now < cfpOpen ) return 'upcoming' ;
16+ if ( now <= cfpClose ) return 'open' ;
17+ return 'closed' ;
18+ }
19+
20+ function getTimelineItemStatus ( milestone , index , now ) {
21+ const nextMilestone = TIMELINE_MILESTONES [ index + 1 ] ;
22+ if ( nextMilestone && now >= nextMilestone . date ) return 'past' ;
23+ if ( now >= milestone . date ) return 'active' ;
24+ return 'upcoming' ;
25+ }
26+
427const Speakers = ( ) => {
528 useScrollAnimation ( ) ;
629
30+ const now = useMemo ( ( ) => new Date ( ) , [ ] ) ;
31+ const cfpStatus = getCfpStatus ( now ) ;
32+
33+ const bannerContent = {
34+ upcoming : { text : `📢 Call for Proposals will be OPEN on ${ TIMELINE_MILESTONES [ 0 ] . label } !` , alertClass : 'alert-success' } ,
35+ open : { text : '📢 Call for Proposals is NOW OPEN!' , alertClass : 'alert-success' } ,
36+ closed : { text : '📢 Call for Proposals is now CLOSED. Thank you for your submissions!' , alertClass : 'alert-warning' } ,
37+ } [ cfpStatus ] ;
38+
39+ const ctaText = {
40+ upcoming : `We can't wait to see your proposal. Submissions open ${ TIMELINE_MILESTONES [ 0 ] . label } !` ,
41+ open : `We can't wait to see your proposal. Submit before ${ TIMELINE_MILESTONES [ 1 ] . label } !` ,
42+ closed : 'The CFP is closed. Stay tuned for speaker announcements!' ,
43+ } [ cfpStatus ] ;
44+
745 return (
846 < >
947 { /* Page Header */ }
@@ -28,13 +66,15 @@ const Speakers = () => {
2866 submit your proposal and we'll review it with care.
2967 </ p >
3068
31- < div className = " alert alert-success" style = { { textAlign : 'left' , margin : 'var(--spacing-lg) 0' } } >
32- < strong > 📢 Call for Proposals will be OPEN this March 01 2026! </ strong > < br />
69+ < div className = { ` alert ${ bannerContent . alertClass } ` } style = { { textAlign : 'left' , margin : 'var(--spacing-lg) 0' } } >
70+ < strong > { bannerContent . text } </ strong > < br />
3371 We're inviting speakers of all experience levels and backgrounds to contribute
3472 to our conference program. Don't be shy – your unique perspective matters!
3573 </ div >
3674
37- < a href = "https://sessionize.com/pycon-camerooon-2026" target = "_blank" rel = "noopener noreferrer" className = "btn btn-primary btn-lg" > Submit Your Proposal</ a >
75+ { cfpStatus !== 'closed' && (
76+ < a href = "https://sessionize.com/pycon-camerooon-2026" target = "_blank" rel = "noopener noreferrer" className = "btn btn-primary btn-lg" > Submit Your Proposal</ a >
77+ ) }
3878 </ div >
3979 </ div >
4080 </ section >
@@ -69,45 +109,22 @@ const Speakers = () => {
69109 </ h3 >
70110
71111 < div className = "timeline" >
72- < div className = "timeline-item" >
73- < div className = "timeline-date" > March 30th, 2026</ div >
74- < div className = "timeline-content" >
75- < h4 > CFP and Call for Sponsors Opens</ h4 >
76- < p > Start submitting your talk and workshop proposals via Sessionize.</ p >
77- </ div >
78- </ div >
79-
80- < div className = "timeline-item" >
81- < div className = "timeline-date" > May 15th, 2026, 11:59 PM WAT</ div >
82- < div className = "timeline-content" >
83- < h4 > CFP Closes</ h4 >
84- < p > Last day to submit your proposals. Don't wait until the last minute!</ p >
85- </ div >
86- </ div >
87-
88- < div className = "timeline-item" >
89- < div className = "timeline-date" > May 30th, 2026</ div >
90- < div className = "timeline-content" >
91- < h4 > Announcement of Speakers</ h4 >
92- < p > Selected speakers will be announced. All submitters will receive notifications.</ p >
93- </ div >
94- </ div >
95-
96- < div className = "timeline-item" >
97- < div className = "timeline-date" > June 15th, 2026</ div >
98- < div className = "timeline-content" >
99- < h4 > Schedule Published</ h4 >
100- < p > The full conference schedule is posted on the PyCon Cameroon website.</ p >
101- </ div >
102- </ div >
103-
104- < div className = "timeline-item" >
105- < div className = "timeline-date" > September 17th-19th, 2026</ div >
106- < div className = "timeline-content" >
107- < h4 > PyCon Cameroon 2026</ h4 >
108- < p > The main event in Yaoundé, Cameroon!</ p >
109- </ div >
110- </ div >
112+ { TIMELINE_MILESTONES . map ( ( milestone , index ) => {
113+ const status = getTimelineItemStatus ( milestone , index , now ) ;
114+ return (
115+ < div className = { `timeline-item timeline-${ status } ` } key = { index } >
116+ < div className = "timeline-date" >
117+ { milestone . label }
118+ { status === 'active' && < span className = "timeline-badge" > Now</ span > }
119+ { status === 'past' && < span className = "timeline-badge timeline-badge-done" > Done</ span > }
120+ </ div >
121+ < div className = "timeline-content" >
122+ < h4 > { milestone . title } </ h4 >
123+ < p > { milestone . description } </ p >
124+ </ div >
125+ </ div >
126+ ) ;
127+ } ) }
111128 </ div >
112129
113130 { /* Session Types */ }
@@ -332,11 +349,13 @@ const Speakers = () => {
332349 < div className = "container text-center" >
333350 < h2 style = { { color : 'white' , marginBottom : 'var(--spacing-sm)' } } > Ready to Share Your Knowledge?</ h2 >
334351 < p style = { { color : 'rgba(255,255,255,0.9)' , fontSize : '1.25rem' , marginBottom : 'var(--spacing-md)' } } >
335- We can't wait to see your proposal. Submit before May 31st, 2026!
352+ { ctaText }
336353 </ p >
337- < a href = "https://sessionize.com/pycon-camerooon-2026" target = "_blank" rel = "noopener noreferrer" className = "btn btn-lg" style = { { background : 'white' , color : 'var(--color-green)' } } >
338- Submit Your Proposal Now
339- </ a >
354+ { cfpStatus !== 'closed' && (
355+ < a href = "https://sessionize.com/pycon-camerooon-2026" target = "_blank" rel = "noopener noreferrer" className = "btn btn-lg" style = { { background : 'white' , color : 'var(--color-green)' } } >
356+ Submit Your Proposal Now
357+ </ a >
358+ ) }
340359 </ div >
341360 </ section >
342361 </ >
0 commit comments