skipped 19 lines 20 20 import BarChartSkeleton from '../BarChart/BarChartSkeleton'; 21 21 import { formatNumber } from 'utils/helpers'; 22 22 import Hint from '../Tooltip/Hint'; 23 + import { useNavigate } from 'react-router-dom'; 23 24 24 25 type Problem = 'vulnerabilities' | 'duplicate' | 'outdated'; 25 26 skipped 4 lines 30 31 }; 31 32 32 33 type Props = { 34 + /* 33 35 name: string; 34 36 version: string; 35 37 desc: string; skipped 3 lines 39 41 name: string; 40 42 image: string; 41 43 }; 44 + */ 42 45 opened?: boolean; 43 46 detailsLoading?: boolean; 47 + sites: Site[]; 48 + flags: { 49 + vulnerable: boolean; 50 + duplicate: boolean; 51 + outdated: boolean; 52 + }; 53 + pkg: { 54 + name: string; 55 + descriptionFull: string; 56 + containingScriptUrl: string; 57 + version: string; 58 + license: string; 59 + licenseDescription: string; 60 + rating: number; 61 + ratingDelta: number; 62 + deps: string[]; // TODO: probably not just string[] 63 + repositoryUrl?: string; 64 + homePageUrl?: string; 65 + npmUrl?: string; 66 + keywords: Array<{ 67 + name: string; 68 + }>; 69 + author: { 70 + name: string; 71 + avatar: string; 72 + }; 73 + }; 74 + totalRatedPackages: number; 44 75 }; 45 76 46 77 // TODO: refactor this (decomposition, props, memoization, etc) 47 78 export default function PackagePreview({ 48 - name, 49 - version, 50 - desc, 51 - problems, 52 - keywords, 53 - author, 79 + / * name, 80 + version, 81 + desc, 82 + problems, 83 + keywords, 84 + author, 85 + opened, 86 + detailsLoading = false, 87 + */ 54 88 opened, 89 + sites, 90 + flags, 91 + pkg, 92 + totalRatedPackages, 55 93 detailsLoading = false, 56 94 }: Props) { 57 95 const [open, setOpen] = useState<boolean>(opened ?? false); 58 96 const [packageDetailsLoading, setPackageDetailsLoading] = useState<boolean>(detailsLoading); 97 + const navigate = useNavigate(); 59 98 60 99 const toggleOpen = () => { 61 100 if (open) { skipped 108 lines 170 209 links: externalLinks, 171 210 }; 172 211 173 - const { script, license, rating, dependencies, packages, sites, links } = loadedData; 212 + / / const { script, license, rating, dependencies, packages, sites, links } = loadedData; 174 213 175 214 return ( 176 215 <div className={clsx(styles.package, open && styles.open)}> skipped 1 lines 178 217 <div className={styles.top} onClick={toggleOpen}> 179 218 <div className={styles.title}> 180 219 <span className={styles.name}> 181 - {name} <span className={styles.version}>{version}</span> 220 + {pkg . name} <span className={styles.version}>{pkg . version}</span> 182 221 </span> 183 222 184 - {problems && ( 223 + {flags && ( // TODO deal with flags 185 224 <span className={styles.problems}> 186 225 {problems.map((problem) => ( 187 226 <ProblemBadge key={problem} problem={problem} /> skipped 3 lines 191 230 </div> 192 231 193 232 <button type='button' className={styles.arrowWrapper} onClick={toggleOpen}> 194 - <Icon kind='arrowDown' width={14} height={8} color='#8E8AA0' className={styles.arrow} /> 233 + <Icon 234 + kind='arrowDown' 235 + style={{ transform: opened ? 'rotate(180deg)' : 'rotate(0)' }} 236 + width={14} 237 + height={8} 238 + color='#8E8AA0' 239 + className={styles.arrow} 240 + /> 195 241 </button> 196 242 </div> 197 243 198 - <div className={styles.desc}>{desc }</div> 244 + <div className={styles.desc}>{pkg . descriptionFull }</div> 199 245 </div> 200 246 201 247 <CSSTransition skipped 13 lines 215 261 {packageDetailsLoading ? ( 216 262 <ScriptSkeleton /> 217 263 ) : ( 218 - <a href='#' className={styles.statLink} target='_blank' rel='noreferrer'> 219 - {script} 264 + <a 265 + href={pkg.containingScriptUrl} 266 + className={styles.statLink} 267 + target='_blank' 268 + rel='noreferrer' 269 + > 270 + {pkg.containingScriptUrl} 220 271 </a> 221 272 )} 222 273 </div> skipped 8 lines 231 282 <LicenceSkeleton /> 232 283 ) : ( 233 284 <> 234 - <div className={styles.statTitle}>{license .title }</div> 235 - <div className={styles.statSubtitle}>{license .subtitle }</div> 285 + <div className={styles.statTitle}>{pkg .license }</div> 286 + <div className={styles.statSubtitle}>{pkg .licenseDescription }</div> 236 287 </> 237 288 )} 238 289 </div> skipped 11 lines 250 301 ) : ( 251 302 <> 252 303 <div className={styles.statTitle}> 253 - {rating.place} 254 - 304 + {pkg.rating} 255 305 <div 256 306 className={clsx( 257 307 styles.statRating, 258 - rating .rankingDelta > 0 ? styles.statRatingGreen : styles.statRatingRed 308 + pkg .ratingDelta > 0 ? styles.statRatingGreen : styles.statRatingRed 259 309 )} 260 310 > 261 311 <Icon skipped 2 lines 264 314 height={12} 265 315 className={styles.statRatingArrow} 266 316 /> 267 - {rating.rankingDelta} 317 + {pkg.ratingDelta} 268 318 </div> 269 319 </div> 270 - <div className={styles.statSubtitle}>out of { formatNumber ( rating . out ) } < / div > 320 + <div className={styles.statSubtitle}> 321 + out of {formatNumber(totalRatedPackages)} 322 + </div> 271 323 </> 272 324 )} 273 325 </div> skipped 2 lines 276 328 <div className={styles.statHeader}> 277 329 <Icon kind='dependency' color='#8E8AA0' className={styles.statIcon} /> 278 330 Dependencies 331 + {packageDetailsLoading ? ( 332 + <ChipGroupSkeleton /> 333 + ) : ( 334 + <ChipGroup> 335 + {pkg.deps.map((dependency) => ( 336 + <Chip size='medium' fontSize='small' font='monospace'> 337 + {dependency} 338 + </Chip> 339 + ))} 340 + </ChipGroup> 341 + )} 279 342 </div> 280 - {packageDetailsLoading ? ( 281 - <ChipGroupSkeleton /> 282 - ) : ( 283 - <ChipGroup> 284 - {dependencies.map((dependency) => ( 285 - <Chip size='medium' fontSize='small' font='monospace'> 286 - {dependency} 287 - </Chip> 288 - ))} 289 - </ChipGroup> 290 - )} 291 343 </div> 292 - </div> 293 344 345 + {/* TODO: separate component 294 346 <div className={styles.stat}> 295 347 <div className={styles.statHeader}> 296 348 <Icon kind='graph' color='#8E8AA0' className={styles.statIcon} /> skipped 4 lines 301 353 {packageDetailsLoading ? <BarChartSkeleton /> : <BarChart bars={packages} />} 302 354 </div> 303 355 </div> 304 - 305 - {/* TODO: add Modules treemap here */} 356 + */} 306 357 307 - <div className={styles.stat}> 308 - <div className={styles.statHeader}>Used on</div> 358 + {/* TODO: add Modules treemap here */} 309 359 310 - {packageDetailsLoading ? ( 311 - <SitesListSkeleton className={styles.usedOnList} /> 312 - ) : ( 313 - <SitesList sites={sites} className={styles.usedOnList} /> 314 - )} 315 - </div> 360 + <div className={styles.stat}> 361 + <div className={styles.statHeader}>Used on</div> 316 362 317 - <div className={styles.actions}> 318 - <div className={styles.links}> 319 363 {packageDetailsLoading ? ( 320 - <LinksSkeleton /> 364 + <SitesListSkeleton className={styles.usedOnList} /> 321 365 ) : ( 322 - links.map(({ href, kind, linkText }) => ( 323 - <a 324 - key={href} 325 - href={href} 326 - className={styles.link} 327 - target='_blank' 328 - rel='noreferrer' 329 - > 330 - <Icon 331 - kind={kind} 332 - width={kind !== 'npm' ? 16 : 32} 333 - height={kind !== 'npm' ? 16 : 32} 334 - color='#212121' 335 - className={styles.linkIcon} 336 - /> 337 - {linkText} 338 - </a> 339 - )) 366 + <SitesList sites={sites} className={styles.usedOnList} /> 340 367 )} 341 368 </div> 342 369 343 - <Button variant='arrow'>Details</Button> 370 + <div className={styles.actions}> 371 + <div className={styles.links}> 372 + {packageDetailsLoading ? ( 373 + <LinksSkeleton /> 374 + ) : ( 375 + <> 376 + {pkg.repositoryUrl && ( 377 + <a 378 + href={pkg.repositoryUrl} 379 + className={styles.link} 380 + target='_blank' 381 + rel='noreferrer' 382 + > 383 + <Icon kind='repository' color='#212121' className={styles.linkIcon} /> 384 + Repository 385 + </a> 386 + )} 387 + 388 + {pkg.homePageUrl && ( 389 + <a 390 + href={pkg.homePageUrl} 391 + className={styles.link} 392 + target='_blank' 393 + rel='noreferrer' 394 + > 395 + <Icon kind='link' color='#212121' className={styles.linkIcon} /> 396 + Homepage 397 + </a> 398 + )} 399 + 400 + {pkg.npmUrl && ( 401 + <a 402 + href={pkg.npmUrl} 403 + className={styles.link} 404 + target='_blank' 405 + rel='noreferrer' 406 + > 407 + <Icon 408 + kind='npm' 409 + width={32} 410 + height={32} 411 + color='#212121' 412 + className={styles.linkIcon} 413 + /> 414 + </a> 415 + )} 416 + </> 417 + )} 418 + </div> 419 + 420 + {/* TODO: should be a <a> link */} 421 + <Button variant='arrow' onClick={() => navigate('/package/' + pkg.name)}> 422 + Details 423 + </Button> 424 + </div> 344 425 </div> 345 426 </div> 346 427 </div> skipped 4 lines 351 432 {/* TODO: not sure how to conditionally render maximum number of keywords (e.g. 5 for 352 433 desktop, 3/4 for tablet, 2 for mobile) based on viewport and update rest number 353 434 of keywords beyond current maximum in Chip */} 354 - {keywords.slice(0 , 5 ).map((keyword ) => ( 355 - <a key = { keyword } href='#' className={styles.tag}> 356 - {keyword} 435 + {pkg . keywords.slice(6 ).map((tag ) => ( 436 + <a href='#' className={styles.tag}> 437 + {tag.name} 357 438 </a> 358 439 ))} 359 - {keywords.slice ( 5 ) . length > 0 && ( 440 + {pkg . keywords.length > 6 && ( 360 441 <Chip variant='info' size='medium' fontWeight='semiBold'> 361 - +{keywords.slice ( 5 ) . length} 442 + +{pkg . keywords.length - 6 } 362 443 </Chip> 363 444 )} 364 445 </div> 365 446 366 447 <div className={styles.author}> 367 - <span className={styles.authorName}>{author.name}</span> 368 - <img className={styles.authorImage} src={author.image } alt='' /> 448 + <span className={styles.authorName}>{pkg . author.name}</span> 449 + <img className={styles.authorImage} src={pkg . author.avatar } alt='' /> 369 450 </div> 370 451 </div> 371 452 </div> skipped 3 lines