@@ -10,7 +10,11 @@ import {
1010 useMediaQuery ,
1111 useTheme ,
1212 Divider ,
13- Link as MuiLink
13+ Link as MuiLink ,
14+ Dialog ,
15+ DialogTitle ,
16+ DialogContent ,
17+ DialogActions
1418} from '@mui/material'
1519import { useNavigate } from 'react-router-dom'
1620import badge from '../../assets/images/badge.png'
@@ -148,6 +152,9 @@ const deriveDisplayNameFromAny = (value: any): string => {
148152 return utilGuess || asString
149153}
150154
155+ // LinkedTrust LinkedIn organization ID for Add to Profile
156+ const LINKEDTRUST_LINKEDIN_ORG_ID = '69351143'
157+
151158const Certificate : React . FC < CertificateProps > = ( {
152159 issuer_name,
153160 subject,
@@ -178,6 +185,13 @@ const Certificate: React.FC<CertificateProps> = ({
178185 const [ snackbarMessage , setSnackbarMessage ] = useState ( '' )
179186 const [ currentUrl , setCurrentUrl ] = useState ( '' )
180187 const [ isOwnerViaSameAs , setIsOwnerViaSameAs ] = useState < boolean | null > ( null )
188+ const [ linkedInPreviewOpen , setLinkedInPreviewOpen ] = useState ( false )
189+ const [ linkedInPreviewData , setLinkedInPreviewData ] = useState < {
190+ name : string
191+ issueDate : string
192+ certUrl : string
193+ certId : string
194+ } | null > ( null )
181195
182196 useEffect ( ( ) => {
183197 setCurrentUrl ( window . location . href )
@@ -273,24 +287,88 @@ const Certificate: React.FC<CertificateProps> = ({
273287 }
274288 }
275289
276- const generateLinkedInShareUrl = ( credentialName : string , url : string ) => {
277- const encodedUrl = encodeURIComponent ( url )
278- const message = encodeURIComponent (
279- `Excited to share my verified ${ credentialName } credential from LinkedTrust! Check it out here: ${ url } Thanks to my validators for confirming my skills!`
280- )
281- return `https://www.linkedin.com/feed/?shareActive=true&shareUrl=${ encodedUrl } &text=${ message } `
290+ const generateLinkedInAddToProfileUrl = ( ) => {
291+ // Build LinkedIn Add to Profile URL for certifications
292+ const params = new URLSearchParams ( )
293+ params . set ( 'startTask' , 'CERTIFICATION_NAME' )
294+
295+ // Build certificate name: claim : aspect : statement[0:50]
296+ const claimObj = claim as any
297+ const parts : string [ ] = [ ]
298+
299+ if ( claimObj ?. claim ) {
300+ parts . push ( claimObj . claim )
301+ }
302+ if ( claimObj ?. aspect ) {
303+ parts . push ( claimObj . aspect )
304+ }
305+ if ( statement ) {
306+ const truncated = statement . length > 50 ? statement . substring ( 0 , 50 ) + '...' : statement
307+ parts . push ( truncated )
308+ }
309+
310+ const certName = parts . length > 0 ? parts . join ( ': ' ) : 'LinkedTrust Credential'
311+ params . set ( 'name' , certName )
312+
313+ // Organization (using ID pulls logo from LinkedIn company page)
314+ params . set ( 'organizationId' , LINKEDTRUST_LINKEDIN_ORG_ID )
315+
316+ // Issue date from effectiveDate
317+ if ( effectiveDate ) {
318+ const date = new Date ( effectiveDate )
319+ params . set ( 'issueYear' , date . getFullYear ( ) . toString ( ) )
320+ params . set ( 'issueMonth' , ( date . getMonth ( ) + 1 ) . toString ( ) )
321+ }
322+
323+ // Certificate ID and URL
324+ if ( claimId ) {
325+ params . set ( 'certId' , claimId . toString ( ) )
326+ }
327+ params . set ( 'certUrl' , currentUrl )
328+
329+ return `https://www.linkedin.com/profile/add?${ params . toString ( ) } `
282330 }
283331
284332 const handleLinkedInPost = ( ) => {
285- let credentialName = 'a new'
286- if ( subject && typeof subject === 'string' && ! subject . includes ( 'http' ) ) {
287- credentialName = subject
333+ // Show preview dialog first
334+ const claimObj = claim as any
335+ const parts : string [ ] = [ ]
336+
337+ if ( claimObj ?. claim ) {
338+ parts . push ( claimObj . claim )
339+ }
340+ if ( claimObj ?. aspect ) {
341+ parts . push ( claimObj . aspect )
288342 }
289- const linkedInShareUrl = generateLinkedInShareUrl ( credentialName , currentUrl )
290- window . open ( linkedInShareUrl , '_blank' )
343+ if ( statement ) {
344+ const truncated = statement . length > 50 ? statement . substring ( 0 , 50 ) + '...' : statement
345+ parts . push ( truncated )
346+ }
347+
348+ const certName = parts . length > 0 ? parts . join ( ': ' ) : 'LinkedTrust Credential'
349+
350+ let issueDate = ''
351+ if ( effectiveDate ) {
352+ const date = new Date ( effectiveDate )
353+ issueDate = date . toLocaleDateString ( 'en-US' , { month : 'long' , year : 'numeric' } )
354+ }
355+
356+ setLinkedInPreviewData ( {
357+ name : certName ,
358+ issueDate,
359+ certUrl : currentUrl ,
360+ certId : claimId ?. toString ( ) || ''
361+ } )
362+ setLinkedInPreviewOpen ( true )
291363 handleClose ( )
292364 }
293365
366+ const handleLinkedInConfirm = ( ) => {
367+ const linkedInUrl = generateLinkedInAddToProfileUrl ( )
368+ window . open ( linkedInUrl , '_blank' )
369+ setLinkedInPreviewOpen ( false )
370+ }
371+
294372 const handleThisIsMe = async ( ) => {
295373 if ( ! userUri || ! subject ) return
296374
@@ -751,6 +829,47 @@ const Certificate: React.FC<CertificateProps> = ({
751829 onClose = { handleClaimDialogClose }
752830 validation = { selectedValidation }
753831 />
832+
833+ { /* LinkedIn Add to Profile Preview Dialog */ }
834+ < Dialog
835+ open = { linkedInPreviewOpen }
836+ onClose = { ( ) => setLinkedInPreviewOpen ( false ) }
837+ maxWidth = "sm"
838+ fullWidth
839+ >
840+ < DialogTitle > Add Certificate to LinkedIn</ DialogTitle >
841+ < DialogContent >
842+ < Typography variant = "body2" color = "textSecondary" sx = { { mb : 2 } } >
843+ This will open LinkedIn to add the following certificate to your profile:
844+ </ Typography >
845+ < Box sx = { { bgcolor : 'grey.100' , p : 2 , borderRadius : 1 } } >
846+ < Typography variant = "subtitle2" color = "textSecondary" > Name</ Typography >
847+ < Typography variant = "body1" sx = { { mb : 1.5 } } > { linkedInPreviewData ?. name } </ Typography >
848+
849+ < Typography variant = "subtitle2" color = "textSecondary" > Issuing Organization</ Typography >
850+ < Typography variant = "body1" sx = { { mb : 1.5 } } > LinkedTrust</ Typography >
851+
852+ { linkedInPreviewData ?. issueDate && (
853+ < >
854+ < Typography variant = "subtitle2" color = "textSecondary" > Issue Date</ Typography >
855+ < Typography variant = "body1" sx = { { mb : 1.5 } } > { linkedInPreviewData . issueDate } </ Typography >
856+ </ >
857+ ) }
858+
859+ < Typography variant = "subtitle2" color = "textSecondary" > Credential ID</ Typography >
860+ < Typography variant = "body1" sx = { { mb : 1.5 } } > { linkedInPreviewData ?. certId } </ Typography >
861+
862+ < Typography variant = "subtitle2" color = "textSecondary" > Credential URL</ Typography >
863+ < Typography variant = "body1" sx = { { wordBreak : 'break-all' } } > { linkedInPreviewData ?. certUrl } </ Typography >
864+ </ Box >
865+ </ DialogContent >
866+ < DialogActions >
867+ < Button onClick = { ( ) => setLinkedInPreviewOpen ( false ) } > Cancel</ Button >
868+ < Button onClick = { handleLinkedInConfirm } variant = "contained" color = "primary" >
869+ Add to LinkedIn
870+ </ Button >
871+ </ DialogActions >
872+ </ Dialog >
754873 </ Card >
755874 </ Container >
756875 )
0 commit comments