Skip to content

Commit 9f02e60

Browse files
authored
Merge pull request #588 from Whats-Cookin/website-testimonial
Website testimonial
2 parents 67d87c4 + 563ed1f commit 9f02e60

File tree

17 files changed

+876
-848
lines changed

17 files changed

+876
-848
lines changed

public/embed/linkedtrust-badge.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@
1212
*/
1313

1414
(function() {
15-
const API_BASE = 'https://live.linkedtrust.us/api';
16-
const SITE_BASE = 'https://live.linkedtrust.us';
15+
// Detect base URL from script src or default to production
16+
const scriptTag = document.currentScript;
17+
const scriptSrc = scriptTag ? scriptTag.src : '';
18+
const srcMatch = scriptSrc.match(/^(https?:\/\/[^\/]+)/);
19+
const detectedBase = srcMatch ? srcMatch[1] : 'https://live.linkedtrust.us';
20+
21+
const API_BASE = detectedBase + '/api';
22+
const SITE_BASE = detectedBase;
1723

1824
class LinkedTrustBadge extends HTMLElement {
1925
constructor() {

src/App.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { checkAuth } from './utils/authUtils'
2323
import CertificateView from './components/Certificate/CertificateView'
2424
import Present from './components/Present'
2525
import RequestRating from './components/RequestRating'
26+
import BadgeEmbed from './components/BadgeEmbed'
2627
import './App.css'
2728

2829
const App = () => {
@@ -144,13 +145,21 @@ const App = () => {
144145
<Route
145146
path='claim'
146147
element={
147-
isAuthenticated ? <Form {...commonProps} /> : <Navigate to='/login' replace state={{ from: location }} />
148+
isAuthenticated ? (
149+
<Form {...commonProps} />
150+
) : (
151+
<Navigate to='/login' replace state={{ from: location }} />
152+
)
148153
}
149154
/>
150155
<Route
151156
path='/rate'
152157
element={
153-
isAuthenticated ? <Rate {...commonProps} /> : <Navigate to='/login' replace state={{ from: location }} />
158+
isAuthenticated ? (
159+
<Rate {...commonProps} />
160+
) : (
161+
<Navigate to='/login' replace state={{ from: location }} />
162+
)
154163
}
155164
/>
156165
<Route
@@ -172,14 +181,11 @@ const App = () => {
172181
<Route path='/certificate/:id' element={<CertificateView />} />
173182
<Route path='/certificatet/:id' element={<CertificateView />} /> {/* Alias for common typo */}
174183
<Route path='/present/:id' element={<Present />} />
184+
<Route path='/badge-embed/:id' element={<BadgeEmbed />} />
175185
<Route
176186
path='/request-rating'
177187
element={
178-
isAuthenticated ? (
179-
<RequestRating />
180-
) : (
181-
<Navigate to='/login' replace state={{ from: location }} />
182-
)
188+
isAuthenticated ? <RequestRating /> : <Navigate to='/login' replace state={{ from: location }} />
183189
}
184190
/>
185191
{/* Catch-all to avoid blank pages */}

src/components/Certificate/CertificateView.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,21 @@ const CertificateView: React.FC = () => {
9292
const claim = data.claim
9393

9494
// Backend provides subjectNode directly with the correct name
95-
const personName = data.subjectNode?.name || '';
95+
const personName = data.subjectNode?.name || ''
9696

9797
// Find issuer from edges - the source edge endNode is the issuer
98-
const sourceEdge = data.claim?.edges?.find((e: any) => e.label === 'source');
99-
const issuerName = sourceEdge?.endNode?.name || '';
98+
const sourceEdge = data.claim?.edges?.find((e: any) => e.label === 'source')
99+
const issuerName = sourceEdge?.endNode?.name || ''
100100

101101
// Separate videos from images
102-
const allMedia = data.images || [];
103-
const videos = allMedia.filter((m: any) =>
104-
m.metadata?.type === 'video' ||
105-
m.url?.includes('.webm') ||
106-
m.url?.includes('.mp4')
107-
);
108-
const images = allMedia.filter((m: any) =>
109-
!m.metadata?.type ||
110-
m.metadata?.type === 'image' ||
111-
(!m.url?.includes('.webm') && !m.url?.includes('.mp4'))
112-
);
102+
const allMedia = data.images || []
103+
const videos = allMedia.filter(
104+
(m: any) => m.metadata?.type === 'video' || m.url?.includes('.webm') || m.url?.includes('.mp4')
105+
)
106+
const images = allMedia.filter(
107+
(m: any) =>
108+
!m.metadata?.type || m.metadata?.type === 'image' || (!m.url?.includes('.webm') && !m.url?.includes('.mp4'))
109+
)
113110

114111
return (
115112
<Box sx={{ p: { xs: 2, sm: 2.5, md: 3 }, maxWidth: '100%', width: '100%', boxSizing: 'border-box', mx: 'auto' }}>

src/components/Certificate/index.tsx

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ const Certificate: React.FC<CertificateProps> = ({
375375

376376
const handleThisIsMe = async () => {
377377
if (!userUri || !subject) return
378-
378+
379379
try {
380380
// Create SAME_AS claim
381381
const { createClaim } = await import('../../api')
@@ -387,11 +387,11 @@ const Certificate: React.FC<CertificateProps> = ({
387387
howKnown: 'VERIFIED_LOGIN',
388388
confidence: 1.0
389389
})
390-
390+
391391
// Show success message
392392
setSnackbarMessage('Identity link created! You can now share this certificate.')
393393
setSnackbarOpen(true)
394-
394+
395395
// Refresh to update UI
396396
setTimeout(() => window.location.reload(), 2000)
397397
} catch (error) {
@@ -407,13 +407,13 @@ const Certificate: React.FC<CertificateProps> = ({
407407
claim: claim?.claim,
408408
statement: statement,
409409
subjectType: subjectType
410-
});
410+
})
411411

412412
// Use the name from backend node resolution
413-
const recipientName = subject_name || 'Certificate Holder';
413+
const recipientName = subject_name || 'Certificate Holder'
414414

415415
// Extract what is being certified (not WHO but WHAT)
416-
const certificationTopic = extractCertificationTopic(statement, claim?.aspect);
416+
const certificationTopic = extractCertificationTopic(statement, claim?.aspect)
417417

418418
const containerMaxWidth = isXl ? 'xl' : 'lg'
419419
const visibleValidationCount = getVisibleValidationCount(isXs, isSm, isMd)
@@ -875,6 +875,15 @@ const Certificate: React.FC<CertificateProps> = ({
875875
</Box>
876876
)}
877877

878+
{/* This is me - shown to logged in users who aren't detected as owner */}
879+
{currentUser && !isOwner && (
880+
<Box onClick={handleThisIsMe} sx={actionButtonStyles}>
881+
<PersonAddIcon sx={{ color: COLORS.primary }} />
882+
<Typography variant='body2' sx={{ color: COLORS.primary, whiteSpace: 'nowrap' }}>
883+
This is Me
884+
</Typography>
885+
</Box>
886+
)}
878887
</Box>
879888

880889
<SharePopover
@@ -910,41 +919,56 @@ const Certificate: React.FC<CertificateProps> = ({
910919
/>
911920

912921
{/* LinkedIn Add to Profile Preview Dialog */}
913-
<Dialog
914-
open={linkedInPreviewOpen}
915-
onClose={() => setLinkedInPreviewOpen(false)}
916-
maxWidth="sm"
917-
fullWidth
918-
>
922+
<Dialog open={linkedInPreviewOpen} onClose={() => setLinkedInPreviewOpen(false)} maxWidth='sm' fullWidth>
919923
<DialogTitle>Add Certificate to LinkedIn</DialogTitle>
920924
<DialogContent>
921-
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
925+
<Typography variant='body2' color='textSecondary' sx={{ mb: 2 }}>
922926
This will open LinkedIn to add the following certificate to your profile:
923927
</Typography>
924928
<Box sx={{ bgcolor: 'grey.100', p: 2, borderRadius: 1 }}>
925-
<Typography variant="subtitle2" color="textSecondary">Name</Typography>
926-
<Typography variant="body1" sx={{ mb: 1.5 }}>{linkedInPreviewData?.name}</Typography>
929+
<Typography variant='subtitle2' color='textSecondary'>
930+
Name
931+
</Typography>
932+
<Typography variant='body1' sx={{ mb: 1.5 }}>
933+
{linkedInPreviewData?.name}
934+
</Typography>
927935

928-
<Typography variant="subtitle2" color="textSecondary">Issuing Organization</Typography>
929-
<Typography variant="body1" sx={{ mb: 1.5 }}>LinkedTrust</Typography>
936+
<Typography variant='subtitle2' color='textSecondary'>
937+
Issuing Organization
938+
</Typography>
939+
<Typography variant='body1' sx={{ mb: 1.5 }}>
940+
LinkedTrust
941+
</Typography>
930942

931943
{linkedInPreviewData?.issueDate && (
932944
<>
933-
<Typography variant="subtitle2" color="textSecondary">Issue Date</Typography>
934-
<Typography variant="body1" sx={{ mb: 1.5 }}>{linkedInPreviewData.issueDate}</Typography>
945+
<Typography variant='subtitle2' color='textSecondary'>
946+
Issue Date
947+
</Typography>
948+
<Typography variant='body1' sx={{ mb: 1.5 }}>
949+
{linkedInPreviewData.issueDate}
950+
</Typography>
935951
</>
936952
)}
937953

938-
<Typography variant="subtitle2" color="textSecondary">Credential ID</Typography>
939-
<Typography variant="body1" sx={{ mb: 1.5 }}>{linkedInPreviewData?.certId}</Typography>
954+
<Typography variant='subtitle2' color='textSecondary'>
955+
Credential ID
956+
</Typography>
957+
<Typography variant='body1' sx={{ mb: 1.5 }}>
958+
{linkedInPreviewData?.certId}
959+
</Typography>
940960

941-
<Typography variant="subtitle2" color="textSecondary">Credential URL</Typography>
942-
<Typography variant="body1" sx={{ wordBreak: 'break-all' }}>{linkedInPreviewData?.certUrl}</Typography>
961+
<Typography variant='subtitle2' color='textSecondary'>
962+
Credential URL
963+
</Typography>
964+
<Typography variant='body1' sx={{ wordBreak: 'break-all' }}>
965+
{linkedInPreviewData?.certUrl}
966+
</Typography>
943967
</Box>
944968
</DialogContent>
945969
<DialogActions>
946970
<Button onClick={() => setLinkedInPreviewOpen(false)}>Cancel</Button>
947-
<Button onClick={handleLinkedInConfirm} variant="contained" color="primary">
971+
<Button onClick={handleLinkedInConfirm} variant='contained' color='primary'>
948972
Add to LinkedIn
949973
</Button>
950974
</DialogActions>

src/components/Form/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ export const Form = ({ toggleSnackbar, setSnackbarMessage, setLoading, onCancel,
302302
{watchSubject && (
303303
<Typography
304304
variant='caption'
305-
onClick={() => setSubjectEntityType(prev => prev === 'PERSON' ? 'ORGANIZATION' : 'PERSON')}
305+
onClick={() => setSubjectEntityType(prev => (prev === 'PERSON' ? 'ORGANIZATION' : 'PERSON'))}
306306
sx={{
307307
color: 'text.secondary',
308308
cursor: 'pointer',
@@ -511,7 +511,7 @@ export const Form = ({ toggleSnackbar, setSnackbarMessage, setLoading, onCancel,
511511
}
512512
fullWidth
513513
sx={{ mb: 2 }}
514-
helperText="Leave blank if unknown. Examples: https://example.com, did:ethr:0x123..."
514+
helperText='Leave blank if unknown. Examples: https://example.com, did:ethr:0x123...'
515515
/>
516516

517517
<LocalizationProvider dateAdapter={AdapterDateFns}>

src/components/GraphDetailModal/index.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,14 @@ const GraphDetailModal: React.FC<GraphDetailModalProps> = ({
128128
{ key: 'subject', label: 'Subject' },
129129
{ key: 'object', label: 'Object' },
130130
{ key: 'aspect', label: 'Aspect' },
131-
{ key: 'confidence', label: 'Confidence', format: (v) => `${Math.round(v * 100)}%` },
132-
{ key: 'stars', label: 'Rating', format: (v) => '★'.repeat(v) + '☆'.repeat(5 - v) },
131+
{ key: 'confidence', label: 'Confidence', format: v => `${Math.round(v * 100)}%` },
132+
{ key: 'stars', label: 'Rating', format: v => '★'.repeat(v) + '☆'.repeat(5 - v) },
133133
{ key: 'howKnown', label: 'How Known' },
134-
{ key: 'effectiveDate', label: 'Date', format: (v) => new Date(v).toLocaleDateString() },
134+
{ key: 'effectiveDate', label: 'Date', format: v => new Date(v).toLocaleDateString() },
135135
{ key: 'amt', label: 'Amount', format: (v, d) => `$${v}${d?.unit ? ' ' + d.unit : ''}` },
136-
{ key: 'sourceURI', label: 'Source', format: (v) => truncateText(v, 50) },
136+
{ key: 'sourceURI', label: 'Source', format: v => truncateText(v, 50) },
137137
{ key: 'author', label: 'Author' },
138-
{ key: 'score', label: 'Score', format: (v) => v.toFixed(2) }
138+
{ key: 'score', label: 'Score', format: v => v.toFixed(2) }
139139
]
140140

141141
return (
@@ -195,14 +195,14 @@ const GraphDetailModal: React.FC<GraphDetailModalProps> = ({
195195
const displayValue = format ? format(value, displayClaimData) : truncateText(String(value), 100)
196196

197197
return (
198-
<Typography
199-
key={key}
200-
variant='body2'
201-
sx={{ mb: 0.5, fontSize: '0.85rem' }}
202-
>
203-
<Box component='span' sx={{ color: 'text.secondary' }}>{label}:</Box>{' '}
198+
<Typography key={key} variant='body2' sx={{ mb: 0.5, fontSize: '0.85rem' }}>
199+
<Box component='span' sx={{ color: 'text.secondary' }}>
200+
{label}:
201+
</Box>{' '}
204202
{key === 'stars' ? (
205-
<Box component='span' sx={{ color: '#FCD34D' }}>{displayValue}</Box>
203+
<Box component='span' sx={{ color: '#FCD34D' }}>
204+
{displayValue}
205+
</Box>
206206
) : (
207207
displayValue
208208
)}

src/components/Present/index.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ const Present: React.FC = () => {
107107
title="LinkedTrust Testimonial Badge"
108108
></iframe>`
109109

110-
const scriptEmbed = `<div id="linkedtrust-badge-${id}"></div>
111-
<script src="${window.location.origin}/embed/badge.js" data-claim-id="${id}"></script>`
110+
const scriptEmbed = `<script src="${window.location.origin}/embed/linkedtrust-badge.js"></script>
111+
<linkedtrust-badge claim-id="${id}"></linkedtrust-badge>`
112112

113113
const htmlBadgeEmbed = `<a href="${claimUrl}" target="_blank" rel="noopener noreferrer" style="display: inline-block; text-decoration: none;">
114114
<div style="
@@ -121,7 +121,9 @@ const Present: React.FC = () => {
121121
max-width: 300px;
122122
">
123123
<div style="font-size: 14px; opacity: 0.9; margin-bottom: 8px;">✓ Verified Testimonial</div>
124-
<div style="font-size: 16px; font-weight: 600; margin-bottom: 8px;">${claim?.statement?.substring(0, 80) || 'Endorsed'}${claim?.statement?.length > 80 ? '...' : ''}</div>
124+
<div style="font-size: 16px; font-weight: 600; margin-bottom: 8px;">${
125+
claim?.statement?.substring(0, 80) || 'Endorsed'
126+
}${claim?.statement?.length > 80 ? '...' : ''}</div>
125127
<div style="font-size: 12px; opacity: 0.8; display: flex; align-items: center; gap: 4px;">
126128
<span>Verified on LinkedTrust</span>
127129
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
@@ -134,7 +136,9 @@ const Present: React.FC = () => {
134136
// Generate request links
135137
const requestValidationUrl = `${window.location.origin}/validate?subject=${BACKEND_BASE_URL}/claims/${id}`
136138
const requestRatingUrl = claim?.subject
137-
? `${window.location.origin}/request-rating?about=${encodeURIComponent(typeof claim.subject === 'string' ? claim.subject : claim.subject.uri)}`
139+
? `${window.location.origin}/request-rating?about=${encodeURIComponent(
140+
typeof claim.subject === 'string' ? claim.subject : claim.subject.uri
141+
)}`
138142
: ''
139143

140144
if (loading) {
@@ -343,7 +347,8 @@ const Present: React.FC = () => {
343347
Request Validation of This Claim
344348
</Typography>
345349
<Typography variant='body2' sx={{ color: theme.palette.text.secondary, mb: 2 }}>
346-
Send this link to someone who can validate this claim. They can add their endorsement with optional video testimony.
350+
Send this link to someone who can validate this claim. They can add their endorsement with optional
351+
video testimony.
347352
</Typography>
348353
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
349354
<TextField

src/components/RequestRating/index.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ const RequestRating: React.FC = () => {
129129
howKnown: 'FIRST_HAND',
130130
effectiveDate: data.effectiveDate.toISOString(),
131131
stars: data.stars || undefined,
132-
sourceURI: data.sourceURI || undefined
132+
sourceURI: data.sourceURI || undefined,
133+
images: [] // Required by useCreateClaim
133134
}
134135

135136
const { message, isSuccess } = await createClaim(payload)
@@ -174,11 +175,7 @@ const RequestRating: React.FC = () => {
174175
<Typography variant='body1' sx={{ color: theme.palette.text.secondary, mb: 3 }}>
175176
Your testimonial has been submitted and will be visible on LinkedTrust.
176177
</Typography>
177-
<Button
178-
variant='contained'
179-
onClick={() => navigate('/feed')}
180-
sx={{ textTransform: 'none' }}
181-
>
178+
<Button variant='contained' onClick={() => navigate('/feed')} sx={{ textTransform: 'none' }}>
182179
Browse Feed
183180
</Button>
184181
</Card>
@@ -356,10 +353,7 @@ const RequestRating: React.FC = () => {
356353
</form>
357354

358355
{/* Footer */}
359-
<Typography
360-
variant='body2'
361-
sx={{ textAlign: 'center', mt: 3, color: theme.palette.text.secondary }}
362-
>
356+
<Typography variant='body2' sx={{ textAlign: 'center', mt: 3, color: theme.palette.text.secondary }}>
363357
Your rating will be publicly visible on{' '}
364358
<MuiLink href='https://linkedtrust.us' target='_blank' rel='noopener noreferrer'>
365359
LinkedTrust

src/components/Validate/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ const Validate = ({ toggleSnackbar, setSnackbarMessage }: IHomeProps) => {
281281
amt?: string | number
282282
score?: number
283283
images?: ImageI[]
284+
videoUrl?: string
284285
}
285286

286287
const payload: PayloadType = {
@@ -290,7 +291,8 @@ const Validate = ({ toggleSnackbar, setSnackbarMessage }: IHomeProps) => {
290291
howKnown: basis, // Start with basis, will be mapped below
291292
effectiveDate: effectiveDateAsString,
292293
claim: CLAIM_VALIDATED,
293-
images
294+
images,
295+
...(videoUrl && { videoUrl }) // Include video URL if recorded
294296
}
295297

296298
// Handle special cases - map basis to actual howKnown
@@ -1000,7 +1002,7 @@ const Validate = ({ toggleSnackbar, setSnackbarMessage }: IHomeProps) => {
10001002
{/* Video Testimonial */}
10011003
<Box sx={{ mb: { xs: 2, sm: 3 } }}>
10021004
<VideoRecorder
1003-
onVideoUploaded={(url) => {
1005+
onVideoUploaded={url => {
10041006
setVideoUrl(url)
10051007
}}
10061008
onVideoRemoved={() => {

0 commit comments

Comments
 (0)