Projects STRLCPY gradejs Commits ab2f8132
🤬
  • ■ ■ ■ ■
    packages/web/src/components/layouts/Error/Error.tsx
    skipped 35 lines
    36 36   </div>
    37 37   
    38 38   <div className={styles.searchWrapper}>
    39  - <SearchBar size='large' placeholder={actionTitle} />
     39 + <SearchBar size='large' value='' placeholder={actionTitle} />
    40 40   </div>
    41 41   </section>
    42 42   
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/SearchResults/SearchResults.tsx
    skipped 3 lines
    4 4  import Container from 'components/ui/Container/Container';
    5 5  import PackagePreview from '../../ui/PackagePreview/PackagePreview';
    6 6  import SearchedResource from '../../ui/SearchedResource/SearchedResource';
    7  -import CardGroup from '../../ui/CardGroup/CardGroup';
    8  -import CardGroups from 'components/ui/CardGroups/CardGroups';
     7 +import { Icon } from '../../ui/Icon/Icon';
    9 8  import LoadingBar, { LoadingBarRef } from 'react-top-loading-bar';
    10 9  import SearchResultsSidebar from 'components/ui/SearchResultsSidebar/SearchResultsSidebar';
    11 10  import { SearchedResourceSkeleton } from '../../ui/SearchedResource/SearchedResourceSkeleton';
    12 11  import { PackagePreviewSkeleton } from '../../ui/PackagePreview/PackagePreviewSkeleton';
    13  -import { CardListSkeleton } from '../../ui/CardList/CardListSkeleton';
    14 12  import StickyDefaultHeader from '../../ui/Header/StickyDefaultHeader';
    15  -import PackagesBySourceCardList from '../../ui/CardList/PackagesBySourceCardList';
    16  -import PopularPackageCardList from '../../ui/CardList/PopularPackageCardList';
    17  -import { RequestWebPageScanOutput } from '../../../services/apiClient';
    18  -import { SubmitHandler } from 'react-hook-form';
    19 13  import { ClientApi } from '../../../services/apiClient';
    20 14  import { ScanStatus, IdentifiedPackage } from 'store/selectors/websiteResults';
    21 15   
    skipped 4 lines
    26 20   packages: IdentifiedPackage[];
    27 21   packagesStats: { total: number; vulnerable: number; outdated: number };
    28 22   vulnerabilities: Record<string, ClientApi.PackageVulnerabilityResponse[]>;
     23 + vulnerabilitiesCount: number;
    29 24   keywordsList: string[];
    30 25   status: ScanStatus;
    31 26   // siteFavicon: string;
    skipped 1 lines
    33 28   
    34 29  export default function SearchResults({
    35 30   isLoading,
    36  - isPending,
    37 31   searchQuery,
    38 32   packages,
    39 33   packagesStats,
    40  - vulnerabilities,
     34 + vulnerabilitiesCount,
    41 35   keywordsList,
    42 36   status,
    43 37  }: Props) {
    44 38   // Documentation: https://github.com/klendi/react-top-loading-bar
    45 39   const loadingRef = useRef<LoadingBarRef>(null);
     40 + const host = new URL(searchQuery).hostname;
     41 + 
     42 + const metaItems = [
     43 + /*{
     44 + icon: <Icon kind='weight' width={24} height={24} />,
     45 + text: '159 kb webpack bundle size',
     46 + },*/
     47 + /*{
     48 + icon: <Icon kind='search' width={24} height={24} color='#212121' />,
     49 + text: '50 scripts found',
     50 + },*/
     51 + {
     52 + icon: <Icon kind='vulnerability' width={24} height={24} color='#F3512E' />,
     53 + text: `${vulnerabilitiesCount} vulnerabilities in ${packagesStats.total} packages`,
     54 + },
     55 + /*{
     56 + icon: <Icon kind='duplicate' color='#F3812E' width={24} height={24} />,
     57 + text: packagesStats.duplicate + ' duplicate packages',
     58 + },*/
     59 + {
     60 + icon: <Icon kind='outdated' color='#F1CE61' stroke='white' width={24} height={24} />,
     61 + text: `${packagesStats.outdated} outdated packages`,
     62 + },
     63 + ];
     64 + 
     65 + const authors: string[] = []; // TODO
     66 + 
     67 + const problems = ['Vulnerabilities', 'Outdated' /*'Duplicate'*/];
    46 68   
    47 69   return (
    48 70   <>
    skipped 8 lines
    57 79   />
    58 80   )}
    59 81   
    60  - <StickyDefaultHeader showSearch query={searchQuery} />
     82 + <StickyDefaultHeader showSearch searchQuery={searchQuery} />
    61 83   
    62 84   <Container>
    63 85   <div className={styles.searchResults}>
    skipped 5 lines
    69 91   />
    70 92   ) : (
    71 93   <SearchedResource
    72  - image={siteFavicon}
     94 + image={/*siteFavicon*/ ''}
    73 95   name={host}
    74  - totalPackages={totalPackages}
    75  - lastScanDate={finishedAt}
     96 + totalPackages={packagesStats.total}
     97 + lastScanDate={status.lastScanDate}
    76 98   />
    77 99   )}
    78 100   </div>
    skipped 1 lines
    80 102   <div className={styles.searchResultsSidebar}>
    81 103   <SearchResultsSidebar
    82 104   metaItems={metaItems}
    83  - keyWords={keyWords}
    84  - vulnerabilities={vulnerabilities}
     105 + keyWords={keywordsList}
     106 + problems={problems}
    85 107   authors={authors}
    86  - loading={loading}
     108 + loading={isLoading}
    87 109   />
    88 110   </div>
    89 111   
    skipped 8 lines
    98 120   <PackagePreviewSkeleton />
    99 121   </>
    100 122   ) : (
    101  - packages.map((pkg, index) => (
    102  - <PackagePreview
    103  - pkg={pkg}
    104  - sites={[] /* TODO */}
    105  - opened={index === 0}
    106  - totalRatedPackages={totalRatedPackages}
    107  - />
    108  - ))
     123 + packages.map((pkg, index) => <PackagePreview pkg={pkg} opened={index === 0} />)
    109 124   )}
    110 125   </div>
    111 126   </div>
    112  - 
     127 + {/*
    113 128   <CardGroups>
    114 129   <CardGroup title='Similar sites'>
    115  - {loading ? (
    116  - <CardListSkeleton />
    117  - ) : (
    118  - <PackagesBySourceCardList cards={state.fetched.similarCards} />
    119  - )}
     130 + {isLoading ? <CardListSkeleton /> : <PackagesBySourceCardList cards={similarCards} />}
    120 131   </CardGroup>
    121 132   
    122 133   <CardGroup title='Popular packages'>
    123  - {loading ? (
    124  - <CardListSkeleton />
    125  - ) : (
    126  - <PopularPackageCardList cards={state.fetched.popularPackages} />
    127  - )}
     134 + {isLoading ? <CardListSkeleton /> : <PopularPackageCardList cards={popularPackages} />}
    128 135   </CardGroup>
    129 136   </CardGroups>
     137 + */}
    130 138   </Container>
    131 139   
    132 140   <Footer />
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/pages/WebsiteResults.tsx
    skipped 15 lines
    16 16   const dispatch = useAppDispatch();
    17 17   
    18 18   /* TODO
    19  - - Отключить фильтры из селекторов (временно) ./
    20  - - Прокинуть данные из packages в packagePreview (все что есть), формализовать тип pkg ./
    21  - - Разобраться с тем как работают фильтрующие компоненты. Мб прикрутить их в отдельный redux-слайс для порядку.
    22  - - Сформулировать типы для фильтров и вернуть их обратно в селекторы.
    23  - */
     19 + - Отключить фильтры из селекторов (временно) ./
     20 + - Прокинуть данные из packages в packagePreview (все что есть), формализовать тип pkg ./
     21 + - Разобраться с тем как работают фильтрующие компоненты. Мб прикрутить их в отдельный redux-слайс для порядку.
     22 + - Сформулировать типы для фильтров и вернуть их обратно в селекторы.
     23 + */
    24 24   
    25  - const { vulnerabilities, keywordsList, status } = useAppSelector(selectors.default);
     25 + const { vulnerabilities, keywordsList, status, vulnerabilitiesCount, lastScanDate } =
     26 + useAppSelector(selectors.default);
    26 27   const packagesFiltered = useAppSelector(selectors.packagesSortedAndFiltered);
    27 28   const packagesStats = useAppSelector(selectors.packagesStats);
    28 29   const { isProtected, isPending, isLoading, isFailed, isInvalid } = useAppSelector(
    skipped 88 lines
    117 118   packages={packagesFiltered}
    118 119   packagesStats={packagesStats}
    119 120   vulnerabilities={vulnerabilities ?? {}}
     121 + vulnerabilitiesCount={vulnerabilitiesCount}
    120 122   keywordsList={keywordsList}
    121  - status={status}
     123 + status={{ status, lastScanDate }}
    122 124   />
    123 125   </>
    124 126   );
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.tsx
    skipped 5 lines
    6 6  import SearchBar from '../SearchBar/SearchBar';
    7 7   
    8 8  export type Props = {
     9 + searchQuery?: string;
    9 10   variant?: 'default' | 'light';
    10 11   showSearch?: boolean;
    11 12   className?: string;
    skipped 2 lines
    14 15   
    15 16  export default function Header({
    16 17   variant = 'default',
     18 + searchQuery,
     19 + className,
    17 20   showSearch = false,
    18  - className,
    19 21   children,
    20 22  }: Props) {
    21 23   return (
    skipped 12 lines
    34 36   
    35 37   {showSearch && (
    36 38   <div className={styles.searchWrapper}>
    37  - <SearchBar />
     39 + <SearchBar value={searchQuery ?? ''} />
    38 40   </div>
    39 41   )}
    40 42   
    skipped 7 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.tsx
    skipped 3 lines
    4 4  import Chip from '../Chip/Chip';
    5 5  import clsx from 'clsx';
    6 6  import ChipGroup from '../ChipGroup/ChipGroup';
    7  -import SitesList from '../SitesList/SitesList';
    8 7  import { CSSTransition } from 'react-transition-group';
    9 8  import Button from '../Button/Button';
    10  -import {
    11  - LicenceSkeleton,
    12  - LinksSkeleton,
    13  - RatingSkeleton,
    14  - ScriptSkeleton,
    15  -} from './PackagePreviewSkeleton';
     9 +import { LicenceSkeleton, LinksSkeleton, ScriptSkeleton } from './PackagePreviewSkeleton';
    16 10  import ProblemBadge from '../ProblemBadge/ProblemBadge';
    17 11  import { ChipGroupSkeleton } from '../ChipGroup/ChipGroupSkeleton';
    18  -import { SitesListSkeleton } from '../SitesList/SitesListSkeleton';
    19  -import BarChart from '../BarChart/BarChart';
    20  -import BarChartSkeleton from '../BarChart/BarChartSkeleton';
    21  -import { formatNumber } from 'utils/helpers';
    22  -import Hint from '../Tooltip/Hint';
    23 12  import { useNavigate } from 'react-router-dom';
    24  - 
    25  -type Problem = 'vulnerabilities' | 'duplicate' | 'outdated';
    26  - 
    27  -type ExternalLink = {
    28  - href: string;
    29  - kind: 'repository' | 'link' | 'npm';
    30  - linkText?: string;
    31  -};
     13 +import { IdentifiedPackage } from 'store/selectors/websiteResults';
    32 14   
    33 15  type Props = {
    34  - /*
    35  - name: string;
    36  - version: string;
    37  - desc: string;
    38  - problems?: Problem[];
    39  - keywords: string[];
    40  - author: {
    41  - name: string;
    42  - image: string;
    43  - };
    44  - */
    45 16   opened?: boolean;
    46 17   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;
     18 + // sites: Site[];
     19 + pkg: IdentifiedPackage;
    75 20  };
    76 21   
     22 +function makeNpmUrl(pkg: IdentifiedPackage) {
     23 + return `https://www.npmjs.com/package/${pkg.name}`;
     24 +}
     25 + 
    77 26  // TODO: refactor this (decomposition, props, memoization, etc)
    78 27  export default function PackagePreview({
    79  - /* name,
    80  - version,
    81  - desc,
    82  - problems,
    83  - keywords,
    84  - author,
    85  - opened,
    86  - detailsLoading = false,
    87  - */
    88 28   opened,
    89  - sites,
    90  - flags,
     29 + //sites,
    91 30   pkg,
    92  - totalRatedPackages,
    93 31   detailsLoading = false,
    94 32  }: Props) {
    95 33   const [open, setOpen] = useState<boolean>(opened ?? false);
    skipped 12 lines
    108 46   }
    109 47   };
    110 48   
    111  - // TODO: Mock API data, remove later
    112  - const externalLinks: ExternalLink[] = [
    113  - { kind: 'repository', href: 'https://github.com/facebook/react/', linkText: 'Repository' },
    114  - { kind: 'link', href: 'https://reactjs.org/', linkText: 'Homepage' },
    115  - { kind: 'npm', href: 'https://www.npmjs.com/package/react' },
    116  - ];
    117  - 
    118  - // TODO: Mock API data, remove later
    119  - const loadedData = {
    120  - script: '/rsrc.php/v3id044/yu/l/en_US/yD2XaVkWQHO.js?_nc_x=Ij3Wp8lg5Kz',
    121  - license: {
    122  - title: 'MIT license',
    123  - subtitle: 'freely distributable',
    124  - },
    125  - rating: {
    126  - place: 385,
    127  - rankingDelta: -4,
    128  - out: 12842,
    129  - },
    130  - dependencies: ['art', 'create-react-class', 'loose-envify', 'scheduler'],
    131  - packages: [
    132  - {
    133  - fill: 1,
    134  - uses: 89912,
    135  - moduleVersion: '21.3.0',
    136  - },
    137  - {
    138  - fill: 0.8,
    139  - uses: 67111,
    140  - moduleVersion: '18.2.0',
    141  - highlighted: true,
    142  - },
    143  - {
    144  - fill: 0.7,
    145  - uses: 44212,
    146  - moduleVersion: '20.1.0',
    147  - },
    148  - {
    149  - fill: 0.6,
    150  - uses: 41129,
    151  - moduleVersion: '18.0.0',
    152  - },
    153  - {
    154  - fill: 0.5,
    155  - uses: 40465,
    156  - moduleVersion: '19.11.2',
    157  - },
    158  - {
    159  - fill: 0.4,
    160  - uses: 38907,
    161  - moduleVersion: '8.1.2',
    162  - vulnerabilities: true,
    163  - },
    164  - ],
    165  - sites: [
    166  - {
    167  - id: '123',
    168  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    169  - name: 'pinterest.com',
    170  - packagesCount: 151,
    171  - },
    172  - {
    173  - id: '456',
    174  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    175  - name: 'pinterest.com',
    176  - packagesCount: 151,
    177  - },
    178  - {
    179  - id: '789',
    180  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    181  - name: 'pinterest.com',
    182  - packagesCount: 151,
    183  - },
    184  - {
    185  - id: '1231',
    186  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    187  - name: 'pinterest.com',
    188  - packagesCount: 151,
    189  - },
    190  - {
    191  - id: '12321',
    192  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    193  - name: 'pinterest.com',
    194  - packagesCount: 151,
    195  - },
    196  - {
    197  - id: '123123',
    198  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    199  - name: 'pinterest.com',
    200  - packagesCount: 151,
    201  - },
    202  - {
    203  - id: '12123132',
    204  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    205  - name: 'pinterest.com',
    206  - packagesCount: 151,
    207  - },
    208  - ],
    209  - links: externalLinks,
    210  - };
    211  - 
    212  - //const { script, license, rating, dependencies, packages, sites, links } = loadedData;
     49 + const deps = Object.keys(
     50 + pkg.registryMetadata?.versionSpecificValues?.[pkg.versionSet[pkg.versionSet.length - 1]]
     51 + .dependencies ?? {}
     52 + );
    213 53   
    214 54   return (
    215 55   <div className={clsx(styles.package, open && styles.open)}>
    skipped 4 lines
    220 60   {pkg.name} <span className={styles.version}>{pkg.version}</span>
    221 61   </span>
    222 62   
    223  - {flags && ( // TODO deal with flags
     63 + {(pkg.vulnerable || pkg.outdated || pkg.duplicate) && (
    224 64   <span className={styles.problems}>
    225  - {problems.map((problem) => (
    226  - <ProblemBadge key={problem} problem={problem} />
    227  - ))}
     65 + {pkg.vulnerable && <ProblemBadge problem='vulnerabilities' />}
     66 + {pkg.outdated && <ProblemBadge problem='outdated' />}
     67 + {/*pkg.duplicate && <ProblemBadge problem='duplicate' />*/}
    228 68   </span>
    229 69   )}
    230 70   </div>
    skipped 10 lines
    241 81   </button>
    242 82   </div>
    243 83   
    244  - <div className={styles.desc}>{pkg.descriptionFull}</div>
     84 + <div className={styles.desc}>{pkg.registryMetadata?.fullDescription}</div>
    245 85   </div>
    246 86   
    247 87   <CSSTransition
    skipped 5 lines
    253 93   >
    254 94   <div className={styles.content}>
    255 95   <div className={styles.contentInner}>
     96 + {/*
    256 97   <div className={styles.stat}>
    257 98   <div className={styles.statHeader}>
    258 99   <Icon kind='script' color='#8E8AA0' className={styles.statIcon} />
    skipped 12 lines
    271 112   </a>
    272 113   )}
    273 114   </div>
     115 + */}
    274 116   
    275 117   <div className={styles.statList}>
    276 118   <div className={clsx(styles.stat, styles.statListItemSmall)}>
    skipped 5 lines
    282 124   <LicenceSkeleton />
    283 125   ) : (
    284 126   <>
    285  - <div className={styles.statTitle}>{pkg.license}</div>
    286  - <div className={styles.statSubtitle}>{pkg.licenseDescription}</div>
     127 + <div className={styles.statTitle}>{pkg.registryMetadata?.license}</div>
     128 + {/* TODO
     129 + <div className={styles.statSubtitle}>
     130 + {pkg.registryMetadata?.licenseDescription}
     131 + </div>*/}
    287 132   </>
    288 133   )}
    289 134   </div>
    290 135   
     136 + {/*
    291 137   <div className={clsx(styles.stat, styles.statListItemSmall)}>
    292 138   <div className={styles.statHeader}>
    293 139   <Icon kind='rating' color='#8E8AA0' className={styles.statIcon} />
    skipped 29 lines
    323 169   </>
    324 170   )}
    325 171   </div>
     172 + */}
    326 173   
    327 174   <div className={clsx(styles.stat, styles.statListItemLarge)}>
    328 175   <div className={styles.statHeader}>
    skipped 3 lines
    332 179   <ChipGroupSkeleton />
    333 180   ) : (
    334 181   <ChipGroup>
    335  - {pkg.deps.map((dependency) => (
     182 + {deps.map((dependency) => (
    336 183   <Chip size='medium' fontSize='small' font='monospace'>
    337 184   {dependency}
    338 185   </Chip>
    skipped 18 lines
    357 204   
    358 205   {/* TODO: add Modules treemap here */}
    359 206   
     207 + {/*
    360 208   <div className={styles.stat}>
    361 209   <div className={styles.statHeader}>Used on</div>
    362 210   
    skipped 3 lines
    366 214   <SitesList sites={sites} className={styles.usedOnList} />
    367 215   )}
    368 216   </div>
     217 + */}
    369 218   
    370 219   <div className={styles.actions}>
    371 220   <div className={styles.links}>
    skipped 1 lines
    373 222   <LinksSkeleton />
    374 223   ) : (
    375 224   <>
    376  - {pkg.repositoryUrl && (
     225 + {pkg.registryMetadata?.repositoryUrl && (
    377 226   <a
    378  - href={pkg.repositoryUrl}
     227 + href={pkg.registryMetadata?.repositoryUrl}
    379 228   className={styles.link}
    380 229   target='_blank'
    381 230   rel='noreferrer'
    skipped 3 lines
    385 234   </a>
    386 235   )}
    387 236   
    388  - {pkg.homePageUrl && (
     237 + {pkg.registryMetadata?.homepageUrl && (
    389 238   <a
    390  - href={pkg.homePageUrl}
     239 + href={pkg.registryMetadata?.homepageUrl}
    391 240   className={styles.link}
    392 241   target='_blank'
    393 242   rel='noreferrer'
    skipped 3 lines
    397 246   </a>
    398 247   )}
    399 248   
    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  - )}
     249 + <a
     250 + href={makeNpmUrl(pkg)}
     251 + className={styles.link}
     252 + target='_blank'
     253 + rel='noreferrer'
     254 + >
     255 + <Icon
     256 + kind='npm'
     257 + width={32}
     258 + height={32}
     259 + color='#212121'
     260 + className={styles.linkIcon}
     261 + />
     262 + </a>
    416 263   </>
    417 264   )}
    418 265   </div>
    419 266   
    420  - {/* TODO: should be a <a> link */}
    421  - <Button variant='arrow' onClick={() => navigate('/package/' + pkg.name)}>
     267 + {/* TODO: should be a <a> link w/ router support */}
     268 + <Button variant='arrow' onClick={() => navigate(`/package/${pkg.name}`)}>
    422 269   Details
    423 270   </Button>
    424 271   </div>
    skipped 7 lines
    432 279   {/* TODO: not sure how to conditionally render maximum number of keywords (e.g. 5 for
    433 280   desktop, 3/4 for tablet, 2 for mobile) based on viewport and update rest number
    434 281   of keywords beyond current maximum in Chip */}
    435  - {pkg.keywords.slice(6).map((tag) => (
     282 + {/* ^ Nearly impossible thing for responsive markup rendered on server.
     283 + Maybe just avoid conditional render? // oklimenko */}
     284 + {pkg.registryMetadata?.keywords?.slice(6).map((tag) => (
    436 285   <a href='#' className={styles.tag}>
    437  - {tag.name}
     286 + {tag}
    438 287   </a>
    439 288   ))}
    440  - {pkg.keywords.length > 6 && (
     289 + {(pkg.registryMetadata?.keywords?.length ?? 0) > 6 && (
    441 290   <Chip variant='info' size='medium' fontWeight='semiBold'>
    442  - +{pkg.keywords.length - 6}
     291 + +{(pkg.registryMetadata?.keywords?.length ?? 0) - 6}
    443 292   </Chip>
    444 293   )}
    445 294   </div>
    446 295   
    447 296   <div className={styles.author}>
    448  - <span className={styles.authorName}>{pkg.author.name}</span>
    449  - <img className={styles.authorImage} src={pkg.author.avatar} alt='' />
     297 + {/* TODO: print all maintainers? Author is not a single entity */}
     298 + <span className={styles.authorName}>{pkg.registryMetadata?.maintainers?.[0].name}</span>
     299 + <img
     300 + className={styles.authorImage}
     301 + src={pkg.registryMetadata?.maintainers?.[0].avatar}
     302 + alt=''
     303 + />
    450 304   </div>
    451 305   </div>
    452 306   </div>
    skipped 3 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/SearchBar/SearchBar.tsx
    skipped 3 lines
    4 4  import clsx from 'clsx';
    5 5   
    6 6  type Props = {
    7  - value?: string;
     7 + value: string;
    8 8   size?: 'default' | 'large';
    9 9   placeholder?: string;
    10 10  };
    11 11   
    12  -// TODO: connect search to redux and get/update with it
    13 12  export default function SearchBar({
    14  - value = 'pinterest.com',
     13 + value,
    15 14   size = 'default',
    16 15   placeholder = 'Start analyzing...',
    17 16  }: Props) {
    skipped 40 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SearchResultsSidebar/SearchResultsSidebar.tsx
    skipped 26 lines
    27 27  type Props = {
    28 28   metaItems: MetaItemProps[];
    29 29   keyWords: string[];
    30  - vulnerabilities: string[];
     30 + problems: string[];
    31 31   authors: string[];
    32 32   loading: boolean;
    33 33  };
    skipped 1 lines
    35 35  export default function SearchResultsSidebar({
    36 36   metaItems,
    37 37   keyWords,
    38  - vulnerabilities,
     38 + problems,
    39 39   authors,
    40 40   loading,
    41 41  }: Props) {
    skipped 71 lines
    113 113   resetGroup={() => setSelectedProblems([])}
    114 114   >
    115 115   <ProblemsList
    116  - keywordsList={vulnerabilities}
     116 + keywordsList={problems}
    117 117   selectedKeywords={selectedProblems}
    118 118   selectHandler={handleProblemsChange}
    119 119   />
    skipped 62 lines
    182 182   ) : (
    183 183   <SidebarCategory categoryName='Problems' selectedKeywords={selectedProblems}>
    184 184   <ProblemsList
    185  - keywordsList={vulnerabilities}
     185 + keywordsList={problems}
    186 186   selectedKeywords={selectedProblems}
    187 187   selectHandler={handleProblemsChange}
    188 188   />
    skipped 39 lines
  • ■ ■ ■ ■ ■
    packages/web/src/store/selectors/websiteResults.ts
    skipped 6 lines
    7 7  //import { SeverityWeightMap } from '../../components/ui/Vulnerability/Vulnerability';
    8 8  import type { ClientApi, GetWebPageScanOutput } from '../../services/apiClient';
    9 9   
     10 +function semverListAsRange(versionList: string[]) {
     11 + if (!versionList.length) {
     12 + return '*';
     13 + }
     14 + 
     15 + if (versionList.length === 1) {
     16 + return versionList[0];
     17 + }
     18 + 
     19 + const sortedVersions = versionList.sort(semver.compare);
     20 + 
     21 + return `${sortedVersions[0]} - ${sortedVersions[sortedVersions.length - 1]}`;
     22 +}
     23 + 
    10 24  export type IdentifiedPackage = ClientApi.ScanResultPackageResponse & {
    11 25   approximateByteSize?: number;
    12 26   outdated?: boolean;
    13 27   vulnerable?: boolean;
    14 28   duplicate?: boolean;
     29 + version?: string;
    15 30  };
    16 31   
    17 32  const getFlags = (state: RootState) => ({
    skipped 1 lines
    19 34   isFailed: state.webpageResults.isFailed,
    20 35  });
    21 36   
    22  -const getScanStatus = (state: RootState) => state.webpageResults.detectionResult?.status;
     37 +const getScanStatus = (state: RootState) => ({
     38 + status: state.webpageResults.detectionResult?.status,
     39 + lastScanDate: state.webpageResults.detectionResult?.finishedAt, // TODO: make sure this is accurate
     40 +});
    23 41  export type ScanStatus = ReturnType<typeof getScanStatus>;
    24 42   
    25 43  const getPackagesMemoized = memoize((result: GetWebPageScanOutput['scanResult']) => {
    skipped 12 lines
    38 56   )
    39 57   );
    40 58   pkg.vulnerable = (result?.vulnerabilities[pkg.name]?.length ?? 0) > 0;
     59 + pkg.version = semverListAsRange(pkg.versionSet);
    41 60   }
    42 61   return packages;
    43 62  });
    skipped 4 lines
    48 67  const getVulnerabilities = (state: RootState) =>
    49 68   state.webpageResults.detectionResult?.scanResult?.vulnerabilities;
    50 69   
    51  -const getSorting = (state: RootState) => state.webpageResults.filters.sort;
     70 +// const getSorting = (state: RootState) => state.webpageResults.filters.sort;
    52 71   
    53  -const getFilter = (state: RootState) => state.webpageResults.filters.filter;
     72 +// const getFilter = (state: RootState) => state.webpageResults.filters.filter;
    54 73   
    55 74  const getKeywords = (state: RootState) => [
    56 75   ...new Set(
    skipped 63 lines
    120 139  export const selectors = {
    121 140   default: createSelector(
    122 141   [getScanStatus, getVulnerabilities, getKeywords],
    123  - (status, vulnerabilities, keywordsList) => ({
     142 + ({ status, lastScanDate }, vulnerabilities, keywordsList) => ({
    124 143   status,
     144 + lastScanDate,
    125 145   vulnerabilities,
    126 146   keywordsList,
     147 + vulnerabilitiesCount: new Set(
     148 + Object.values(vulnerabilities ?? {})
     149 + .flat()
     150 + .map((v) => v.osvId)
     151 + ).size,
    127 152   })
    128 153   ),
    129 154   stateFlags: createSelector(
    130 155   [getScanStatus, getPackages, getFlags],
    131  - (scanStatus, packages, flags) => ({
     156 + ({ status }, packages, flags) => ({
    132 157   ...flags,
    133 158   isInvalid: packages && packages.length === 0,
    134  - isPending: !scanStatus || scanStatus === 'pending',
    135  - isProtected: scanStatus === 'protected',
     159 + isPending: !status || status === 'pending',
     160 + isProtected: status === 'protected',
    136 161   })
    137 162   ),
    138 163   packagesStats: createSelector([getPackages], (packages = []) => ({
    skipped 2 lines
    141 166   outdated: packages.filter((pkg) => !!pkg.outdated).length,
    142 167   })),
    143 168   packagesSortedAndFiltered: createSelector(
    144  - [getPackages, getVulnerabilities, getSorting, getFilter],
     169 + [getPackages /*, getVulnerabilities, getSorting, getFilter*/],
    145 170   (packages /*, vulnerabilities, sorting, filter*/) => packages /* &&
    146 171   vulnerabilities &&
    147 172   filterModes[filter](sortingModes[sorting](packages, vulnerabilities))*/
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/slices/websiteResults.ts
    skipped 2 lines
    3 3  import { trackCustomEvent } from '../../services/analytics';
    4 4   
    5 5  type WebsiteResultsState = {
    6  - filters: typeof DefaultFiltersAndSorters;
     6 + filters: {}; // typeof DefaultFiltersAndSorters;
    7 7   isFailed: boolean;
    8 8   isLoading: boolean;
    9 9   detectionResult?: GetWebPageScanOutput;
    10 10  };
    11 11   
    12 12  const initialState: WebsiteResultsState = {
    13  - filters: { ...DefaultFiltersAndSorters },
     13 + filters: {}, // { ...DefaultFiltersAndSorters },
    14 14   isFailed: false,
    15 15   isLoading: false,
    16 16   detectionResult: undefined,
    skipped 34 lines
    51 51   initialState,
    52 52   reducers: {
    53 53   resetFilters(state) {
    54  - state.filters = { ...DefaultFiltersAndSorters };
     54 + state.filters = {
     55 + /*...DefaultFiltersAndSorters*/
     56 + };
    55 57   },
    56  - applyFilters(state, action: PayloadAction<FiltersState>) {
    57  - state.filters = action.payload;
     58 + applyFilters(_state) {
     59 + // state.filters = action.payload;
    58 60   },
    59 61   },
    60 62   extraReducers(builder) {
    skipped 22 lines
Please wait...
Page is in error, reload to recover