@@ -28,6 +28,30 @@ interface SpeakersSectionProps {
2828 confYear : string ;
2929}
3030
31+ // Pick a topic emoji from the session title. Falls back to 🎤.
32+ const getTopicEmoji = ( title : string | undefined ) : string => {
33+ if ( ! title ) return "🎤" ;
34+ const t = title . toLowerCase ( ) ;
35+ if ( / k e y n o t e | f e a t u r e d / . test ( t ) ) return "🎤" ;
36+ if ( / s p a c e l y | b e s t p r a c t i c e s / . test ( t ) ) return "🚀" ;
37+ if ( / b e h i n d t h e s c e n e s | u n d e r t h e h o o d | a r c h i t e c t u r e / . test ( t ) ) return "🏗️" ;
38+ if ( / d i s t r i b u t e d l o c k | s a g a | c o o r d i n a t i o n / . test ( t ) ) return "🔒" ;
39+ if ( / c h a n g e f e e d / . test ( t ) ) return "🔄" ;
40+ if ( / q u e r y | i n d e x / . test ( t ) ) return "🔍" ;
41+ if ( / f r a u d | e v e n t s o u r c / . test ( t ) ) return "⚡" ;
42+ if ( / m i c r o s e r v i c e | e v e n t - d r i v e n / . test ( t ) ) return "📡" ;
43+ if ( / m i g r a t / . test ( t ) ) return "🔀" ;
44+ if ( / i m p o r t / . test ( t ) ) return "📥" ;
45+ if ( / m c p | i d e n t i t y | s e c u r i t y | s e c u r / . test ( t ) ) return "🛡️" ;
46+ if ( / r a g | v e c t o r | h y b r i d s e a r c h / . test ( t ) ) return "🧭" ;
47+ if ( / m e m o r y | a g e n t | l l m | \b a i \b / . test ( t ) ) return "🤖" ;
48+ if ( / m u l t i .? c l o u d | a n y c l o u d | o n e c o d e b a s e / . test ( t ) ) return "☁️" ;
49+ if ( / c o s t | r u \b / . test ( t ) ) return "💰" ;
50+ if ( / d a t a m o d e l | m o d e l i n g / . test ( t ) ) return "🗂️" ;
51+ if ( / d e v ( e l o p m e n t ) ? e n v | e n v i r o n m e n t | c o p i l o t | t o o l / . test ( t ) ) return "🛠️" ;
52+ return "🎤" ;
53+ } ;
54+
3155const SpeakersSection = ( { confYear } : SpeakersSectionProps ) => {
3256 const allSpeakers : Speaker [ ] = speakersData as unknown as Speaker [ ] ;
3357 const speakers = allSpeakers . filter ( ( s ) => s . confirmed !== false ) ;
@@ -229,6 +253,95 @@ const SpeakersSection = ({ confYear }: SpeakersSectionProps) => {
229253 < p className = { styles . speakerModalBioText } > { selected . bio } </ p >
230254 </ div >
231255 ) }
256+
257+ { ( ( ) => {
258+ const shareHash = `#speaker/${ selected . slug } ` ;
259+ const shareUrl =
260+ typeof window !== "undefined"
261+ ? `${ window . location . origin } ${ window . location . pathname } ${ shareHash } `
262+ : shareHash ;
263+ const sessionTitle = selected . session ?. title ;
264+ const roleCompany = [ selected . role , selected . company ] . filter ( Boolean ) . join ( ", " ) ;
265+ const topicEmoji = getTopicEmoji ( sessionTitle ) ;
266+ // Short-form for X (keep under ~240 chars before URL)
267+ const twitterText = sessionTitle
268+ ? `${ topicEmoji } Catch ${ selected . name } ${ roleCompany ? ` (${ roleCompany } )` : "" } at #AzureCosmosDBConf ${ confYear } : "${ sessionTitle } "`
269+ : `${ topicEmoji } Catch ${ selected . name } ${ roleCompany ? ` (${ roleCompany } )` : "" } at #AzureCosmosDBConf ${ confYear } .` ;
270+ // Longer-form for LinkedIn
271+ const linkedInText = sessionTitle
272+ ? `${ topicEmoji } I'm looking forward to ${ selected . name } ${ roleCompany ? ` (${ roleCompany } )` : "" } at Azure Cosmos DB Conf ${ confYear } — session: "${ sessionTitle } ". Free, virtual, April 28. #AzureCosmosDBConf #AzureCosmosDB`
273+ : `${ topicEmoji } I'm looking forward to ${ selected . name } ${ roleCompany ? ` (${ roleCompany } )` : "" } at Azure Cosmos DB Conf ${ confYear } . Free, virtual, April 28. #AzureCosmosDBConf #AzureCosmosDB` ;
274+ const emailSubject = sessionTitle
275+ ? `${ selected . name } at Azure Cosmos DB Conf ${ confYear } — ${ sessionTitle } `
276+ : `${ selected . name } at Azure Cosmos DB Conf ${ confYear } ` ;
277+ const emailBody = `${ linkedInText } \n\n${ shareUrl } ` ;
278+ const encodedUrl = encodeURIComponent ( shareUrl ) ;
279+ const twitterUrl = `https://twitter.com/intent/tweet?url=${ encodedUrl } &text=${ encodeURIComponent ( twitterText ) } ` ;
280+ const linkedInUrl = `https://www.linkedin.com/feed/?shareActive=true&text=${ encodeURIComponent (
281+ `${ linkedInText } \n\n${ shareUrl } `
282+ ) } `;
283+ const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${ encodedUrl } ` ;
284+ const mailUrl = `mailto:?subject=${ encodeURIComponent ( emailSubject ) } &body=${ encodeURIComponent ( emailBody ) } ` ;
285+ const copyLink = ( e : React . MouseEvent < HTMLButtonElement > ) => {
286+ e . preventDefault ( ) ;
287+ if ( typeof window === "undefined" || ! navigator . clipboard ) return ;
288+ navigator . clipboard . writeText ( shareUrl ) . catch ( ( ) => { } ) ;
289+ } ;
290+ return (
291+ < div className = { styles . newsCardShareRow } aria-label = "Share this speaker" >
292+ < span className = { styles . newsCardShareLabel } > Share:</ span >
293+ < a
294+ className = { styles . newsCardShareButton }
295+ href = { twitterUrl }
296+ target = "_blank"
297+ rel = "noopener noreferrer"
298+ title = "Share on X (Twitter)"
299+ aria-label = "Share on X"
300+ >
301+ 𝕏
302+ </ a >
303+ < a
304+ className = { styles . newsCardShareButton }
305+ href = { linkedInUrl }
306+ target = "_blank"
307+ rel = "noopener noreferrer"
308+ title = "Share on LinkedIn"
309+ aria-label = "Share on LinkedIn"
310+ >
311+ in
312+ </ a >
313+ < a
314+ className = { styles . newsCardShareButton }
315+ href = { facebookUrl }
316+ target = "_blank"
317+ rel = "noopener noreferrer"
318+ title = "Share on Facebook"
319+ aria-label = "Share on Facebook"
320+ >
321+ f
322+ </ a >
323+ < a
324+ className = { styles . newsCardShareButton }
325+ href = { mailUrl }
326+ target = "_blank"
327+ rel = "noopener noreferrer"
328+ title = "Share by email"
329+ aria-label = "Share by email"
330+ >
331+ ✉
332+ </ a >
333+ < button
334+ type = "button"
335+ className = { styles . newsCardShareButton }
336+ onClick = { copyLink }
337+ title = "Copy link to this speaker"
338+ aria-label = "Copy link"
339+ >
340+ 🔗
341+ </ button >
342+ </ div >
343+ ) ;
344+ } ) ( ) }
232345 </ div >
233346 </ div >
234347 ) }
0 commit comments