1
0
mirror of https://github.com/SomboChea/ui synced 2024-11-28 08:54:27 +07:00

Merge pull request #77 from griffithtp/refactor/74_linting-warnings

refactor: 74 linting warnings
This commit is contained in:
Juan Picado @jotadeveloper 2019-06-27 08:56:53 +02:00 committed by GitHub
commit a0f0c80e2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 321 additions and 237 deletions

View File

@ -39,8 +39,8 @@ const register = (url, method = 'get', options = {}) => {
* Bind API methods * Bind API methods
*/ */
class API { class API {
request() { public request(...rest) {
return register.call(null, ...arguments); return register.call(null, ...rest);
} }
} }

View File

@ -2,6 +2,6 @@
* Mock response for logo api * Mock response for logo api
* @returns {promise} * @returns {promise}
*/ */
export default function() { export default function<T>(): Promise<T> {
return Promise.resolve('http://localhost/-/static/logo.png'); return Promise.resolve('http://localhost/-/static/logo.png');
} }

View File

@ -7,7 +7,7 @@ import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__
jest.mock('../utils/storage', () => { jest.mock('../utils/storage', () => {
class LocalStorageMock { class LocalStorageMock {
store: object; private store: object;
public constructor() { public constructor() {
this.store = {}; this.store = {};
} }
@ -43,7 +43,7 @@ describe('App', () => {
expect(wrapper.state().showLoginModal).toBeFalsy(); expect(wrapper.state().showLoginModal).toBeFalsy();
handleToggleLoginModal(); handleToggleLoginModal();
expect(wrapper.state('showLoginModal')).toBeTruthy(); expect(wrapper.state('showLoginModal')).toBeTruthy();
expect(wrapper.state('error')).toEqual({}); expect(wrapper.state('error')).toEqual(undefined);
}); });
test('isUserAlreadyLoggedIn: token already available in storage', async () => { test('isUserAlreadyLoggedIn: token already available in storage', async () => {

View File

@ -14,14 +14,26 @@ import '../styles/typeface-roboto.scss';
import '../styles/main.scss'; import '../styles/main.scss';
import 'normalize.css'; import 'normalize.css';
import Footer from '../components/Footer'; import Footer from '../components/Footer';
import { FormError } from 'src/components/Login/Login';
export const AppContext = React.createContext<null>(null); export const AppContext = React.createContext<{}>({});
export const AppContextProvider = AppContext.Provider; export const AppContextProvider = AppContext.Provider;
export const AppContextConsumer = AppContext.Consumer; export const AppContextConsumer = AppContext.Consumer;
export default class App extends Component<any, any> { export interface AppStateInterface {
public state = { error?: FormError;
error: {}, logoUrl: string;
user: {
username?: string;
};
scope: string;
showLoginModal: boolean;
isUserLoggedIn: boolean;
packages: [];
isLoading: boolean;
}
export default class App extends Component<{}, AppStateInterface> {
public state: AppStateInterface = {
// @ts-ignore // @ts-ignore
logoUrl: window.VERDACCIO_LOGO, logoUrl: window.VERDACCIO_LOGO,
user: {}, user: {},
@ -49,7 +61,7 @@ export default class App extends Component<any, any> {
public render(): React.ReactElement<HTMLDivElement> { public render(): React.ReactElement<HTMLDivElement> {
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state; const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
const context: any = { isUserLoggedIn, packages, logoUrl, user, scope }; const context = { isUserLoggedIn, packages, logoUrl, user, scope };
return ( return (
// @ts-ignore // @ts-ignore
@ -112,7 +124,6 @@ export default class App extends Component<any, any> {
this.setState(prevState => ({ this.setState(prevState => ({
// @ts-ignore // @ts-ignore
showLoginModal: !prevState.showLoginModal, showLoginModal: !prevState.showLoginModal,
error: {},
})); }));
}; };

View File

@ -25,7 +25,7 @@ const ACTIONS = {
}, },
}; };
class ActionBar extends Component<any, any> { class ActionBar extends Component {
public render(): ReactElement<HTMLElement> { public render(): ReactElement<HTMLElement> {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
@ -36,7 +36,7 @@ class ActionBar extends Component<any, any> {
); );
} }
private renderIconsWithLink(link: string, component: any): ReactElement<HTMLElement> { private renderIconsWithLink(link: string, component: JSX.Element): ReactElement<HTMLElement> {
return ( return (
<a href={link} target={'_blank'}> <a href={link} target={'_blank'}>
{component} {component}

View File

@ -1,4 +1,4 @@
import React, { Component, ReactNode } from 'react'; import React, { Component, ReactNode, ReactElement } from 'react';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
@ -8,18 +8,18 @@ import { DetailContextConsumer } from '../../pages/version/Version';
import { Heading, AuthorListItem } from './styles'; import { Heading, AuthorListItem } from './styles';
import { isEmail } from '../../utils/url'; import { isEmail } from '../../utils/url';
class Authors extends Component<any, any> { class Authors extends Component {
render() { public render(): ReactElement<HTMLElement> {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
{(context: any) => { {context => {
return context && context.packageMeta && this.renderAuthor(context.packageMeta); return context && context.packageMeta && this.renderAuthor(context.packageMeta);
}} }}
</DetailContextConsumer> </DetailContextConsumer>
); );
} }
renderLinkForMail(email: string, avatarComponent: ReactNode, packageName: string, version: string) { public renderLinkForMail(email: string, avatarComponent: ReactNode, packageName: string, version: string): ReactElement<HTMLElement> | ReactNode {
if (!email || isEmail(email) === false) { if (!email || isEmail(email) === false) {
return avatarComponent; return avatarComponent;
} }
@ -31,7 +31,7 @@ class Authors extends Component<any, any> {
); );
} }
renderAuthor = packageMeta => { public renderAuthor = packageMeta => {
const { author, name: packageName, version } = packageMeta.latest; const { author, name: packageName, version } = packageMeta.latest;
if (!author) { if (!author) {

View File

@ -7,8 +7,8 @@ import MenuItem from '@material-ui/core/MenuItem';
import { fontWeight } from '../../utils/styles/sizes'; import { fontWeight } from '../../utils/styles/sizes';
import { Wrapper, InputField, SuggestionContainer } from './styles'; import { Wrapper, InputField, SuggestionContainer } from './styles';
export interface Props { interface Props {
suggestions: any[]; suggestions: unknown[];
suggestionsLoading?: boolean; suggestionsLoading?: boolean;
suggestionsLoaded?: boolean; suggestionsLoaded?: boolean;
suggestionsError?: boolean; suggestionsError?: boolean;
@ -16,17 +16,17 @@ export interface Props {
color?: string; color?: string;
value?: string; value?: string;
placeholder?: string; placeholder?: string;
startAdornment?: any; startAdornment?: JSX.Element;
disableUnderline?: boolean; disableUnderline?: boolean;
onChange?: (event: KeyboardEvent<HTMLInputElement>, { newValue, method }: { newValue: string; method: string }) => void; onChange?: (event: KeyboardEvent<HTMLInputElement>, { newValue, method }: { newValue: string; method: string }) => void;
onSuggestionsFetch?: ({ value: string }) => Promise<void>; onSuggestionsFetch?: ({ value: string }) => Promise<void>;
onCleanSuggestions?: () => void; onCleanSuggestions?: () => void;
onClick?: (event: KeyboardEvent<HTMLInputElement>, { suggestionValue, method }: { suggestionValue: any[]; method: string }) => void; onClick?: (event: KeyboardEvent<HTMLInputElement>, { suggestionValue, method }: { suggestionValue: string[]; method: string }) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void; onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: KeyboardEvent<HTMLInputElement>) => void; onBlur?: (event: KeyboardEvent<HTMLInputElement>) => void;
} }
const renderInputComponent = inputProps => { const renderInputComponent = (inputProps): JSX.Element => {
const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps; const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps;
return ( return (
<InputField <InputField
@ -46,7 +46,7 @@ const renderInputComponent = inputProps => {
const getSuggestionValue = (suggestion): string => suggestion.name; const getSuggestionValue = (suggestion): string => suggestion.name;
const renderSuggestion = (suggestion, { query, isHighlighted }) => { const renderSuggestion = (suggestion, { query, isHighlighted }): JSX.Element => {
const matches = match(suggestion.name, query); const matches = match(suggestion.name, query);
const parts = parse(suggestion.name, matches); const parts = parse(suggestion.name, matches);
return ( return (
@ -68,7 +68,7 @@ const renderSuggestion = (suggestion, { query, isHighlighted }) => {
); );
}; };
const renderMessage = message => { const renderMessage = (message): JSX.Element => {
return ( return (
<MenuItem component="div" selected={false}> <MenuItem component="div" selected={false}>
<div>{message}</div> <div>{message}</div>
@ -98,7 +98,7 @@ const AutoComplete = ({
suggestionsLoading = false, suggestionsLoading = false,
suggestionsLoaded = false, suggestionsLoaded = false,
suggestionsError = false, suggestionsError = false,
}: Props) => { }: Props): JSX.Element => {
const autosuggestProps = { const autosuggestProps = {
renderInputComponent, renderInputComponent,
suggestions, suggestions,
@ -119,7 +119,7 @@ const AutoComplete = ({
}; };
// this format avoid arrow function eslint rule // this format avoid arrow function eslint rule
function renderSuggestionsContainer({ containerProps, children, query }) { function renderSuggestionsContainer({ containerProps, children, query }): JSX.Element {
return ( return (
<SuggestionContainer {...containerProps} square={true}> <SuggestionContainer {...containerProps} square={true}>
{suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)} {suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)}

View File

@ -12,7 +12,7 @@ interface Props {
children?: React.ReactNode; children?: React.ReactNode;
} }
const renderText: React.FC<any> = (text: string, children: React.ReactNode): React.ReactElement<HTMLElement> => { const renderText = (text, children): JSX.Element => {
if (children) { if (children) {
return <ClipBoardCopyText>{children}</ClipBoardCopyText>; return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
} }

View File

@ -1,5 +1,5 @@
import React, { Component, Fragment, ReactElement } from 'react'; import React, { Component, Fragment, ReactElement } from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter, RouteProps } from 'react-router-dom';
import CardContent from '@material-ui/core/CardContent'; import CardContent from '@material-ui/core/CardContent';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version'; import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
@ -7,8 +7,19 @@ import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/ver
import { CardWrap, Heading, Tags, Tag } from './styles'; import { CardWrap, Heading, Tags, Tag } from './styles';
import NoItems from '../NoItems'; import NoItems from '../NoItems';
class DepDetail extends Component<any, any> { interface DepDetailProps {
constructor(props: any) { name: string;
version: string;
onLoading: () => void;
history: string[];
}
interface DepDetailState {
name: string;
version: string;
}
class DepDetail extends Component<DepDetailProps & RouteProps, DepDetailState> {
constructor(props: DepDetailProps) {
super(props); super(props);
const { name, version } = this.props; const { name, version } = this.props;
@ -33,16 +44,16 @@ class DepDetail extends Component<any, any> {
}; };
} }
const WrapperDependencyDetail = withRouter(DepDetail); const WrapperDependencyDetail = withRouter<any>(DepDetail);
class DependencyBlock extends Component<any, any> { class DependencyBlock extends Component<{ title: string; dependencies: [] }> {
public render(): ReactElement<HTMLElement> { public render(): ReactElement<HTMLElement> {
const { dependencies, title } = this.props; const { dependencies, title } = this.props;
const deps = Object.entries(dependencies); const deps = Object.entries(dependencies) as [];
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
{({ enableLoading }: any) => { {({ enableLoading }) => {
return ( return (
<CardWrap> <CardWrap>
<CardContent> <CardContent>
@ -56,15 +67,15 @@ class DependencyBlock extends Component<any, any> {
); );
} }
private renderTags = (deps: any, enableLoading: any) => private renderTags = (deps: [], enableLoading?: () => void) =>
deps.map(dep => { deps.map(dep => {
const [name, version] = dep; const [name, version] = dep as [string, string];
return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />; return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />;
}); });
} }
class Dependencies extends Component<any, any> { class Dependencies extends Component {
public state = { public state = {
tabPosition: 0, tabPosition: 0,
}; };
@ -79,7 +90,7 @@ class Dependencies extends Component<any, any> {
); );
} }
private checkDependencyLength(dependency: Record<string, any> = {}): boolean { private checkDependencyLength<T>(dependency: Record<string, T> = {}): boolean {
return Object.keys(dependency).length > 0; return Object.keys(dependency).length > 0;
} }

View File

@ -14,7 +14,7 @@ interface DetailContainerState {
tabPosition: number; tabPosition: number;
} }
class DetailContainer extends Component<any, DetailContainerState> { class DetailContainer<P> extends Component<P, DetailContainerState> {
public state = { public state = {
tabPosition: 0, tabPosition: 0,
}; };
@ -29,7 +29,7 @@ class DetailContainer extends Component<any, DetailContainerState> {
); );
} }
private handleChange = (event: any, tabPosition: number) => { private handleChange = (event: React.ChangeEvent<{}>, tabPosition: number) => {
event.preventDefault(); event.preventDefault();
this.setState({ tabPosition }); this.setState({ tabPosition });
}; };

View File

@ -11,18 +11,21 @@ import { isEmail } from '../../utils/url';
interface Props { interface Props {
type: 'contributors' | 'maintainers'; type: 'contributors' | 'maintainers';
} }
interface State {
visibleDevs: number;
}
class Developers extends Component<Props, any> { class Developers extends Component<Props, State> {
state = { public state = {
visibleDevs: 6, visibleDevs: 6,
}; };
render() { public render(): JSX.Element {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
{({ packageMeta }: any) => { {({ packageMeta }) => {
const { type } = this.props; const { type } = this.props;
const developerType = packageMeta.latest[type]; const developerType = packageMeta && packageMeta.latest[type];
if (!developerType || developerType.length === 0) return null; if (!developerType || developerType.length === 0) return null;
return this.renderDevelopers(developerType, packageMeta); return this.renderDevelopers(developerType, packageMeta);
}} }}
@ -30,11 +33,11 @@ class Developers extends Component<Props, any> {
); );
} }
handleLoadMore = () => { public handleLoadMore = () => {
this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 })); this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 }));
}; };
renderDevelopers = (developers, packageMeta) => { private renderDevelopers = (developers, packageMeta) => {
const { type } = this.props; const { type } = this.props;
const { visibleDevs } = this.state; const { visibleDevs } = this.state;
return ( return (
@ -54,7 +57,7 @@ class Developers extends Component<Props, any> {
); );
}; };
renderLinkForMail(email, avatarComponent, packageName, version) { private renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element {
if (!email || isEmail(email) === false) { if (!email || isEmail(email) === false) {
return avatarComponent; return avatarComponent;
} }
@ -65,7 +68,7 @@ class Developers extends Component<Props, any> {
); );
} }
renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => { private renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
const { name: packageName, version } = packageMeta.latest; const { name: packageName, version } = packageMeta.latest;
const avatarComponent = <Avatar aria-label={name} src={avatar} />; const avatarComponent = <Avatar aria-label={name} src={avatar} />;

View File

@ -2,22 +2,23 @@ import React, { Component } from 'react';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import { DetailContextConsumer } from '../../pages/version/Version'; import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { Heading, DistListItem, DistChips } from './styles'; import { Heading, DistListItem, DistChips } from './styles';
import fileSizeSI from '../../utils/file-size'; import fileSizeSI from '../../utils/file-size';
import { PackageMetaInterface } from 'types/packageMeta';
class Dist extends Component<any, any> { class Dist extends Component {
render() { public render(): JSX.Element {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
{(context: any) => { {(context: Partial<VersionPageConsumerProps>) => {
return this.renderDist(context); return context && context.packageMeta && this.renderDist(context.packageMeta);
}} }}
</DetailContextConsumer> </DetailContextConsumer>
); );
} }
renderChips(dist: any, license: string) { private renderChips(dist, license: string): JSX.Element | never[] {
const distDict = { const distDict = {
'file-count': dist.fileCount, 'file-count': dist.fileCount,
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize), size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
@ -43,8 +44,8 @@ class Dist extends Component<any, any> {
return chipsList; return chipsList;
} }
renderDist = ({ packageMeta }: any) => { private renderDist = (packageMeta: PackageMetaInterface) => {
const { dist = {}, license } = packageMeta.latest; const { dist, license } = packageMeta && packageMeta.latest;
return ( return (
<List subheader={<Heading variant="subheading">{'Latest Distribution'}</Heading>}> <List subheader={<Heading variant="subheading">{'Latest Distribution'}</Heading>}>

View File

@ -3,7 +3,7 @@ import React from 'react';
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles'; import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
import { goToVerdaccioWebsite } from '../../utils/windows'; import { goToVerdaccioWebsite } from '../../utils/windows';
const renderTooltip = () => ( const renderTooltip = (): JSX.Element => (
<ToolTip> <ToolTip>
<Earth name="earth" size="md" /> <Earth name="earth" size="md" />
<Flags> <Flags>
@ -22,7 +22,7 @@ const ON_LABEL = 'on';
const HEARTH_EMOJI = '♥'; const HEARTH_EMOJI = '♥';
// @ts-ignore // @ts-ignore
const renderRight = (version = window.VERDACCIO_VERSION) => { const renderRight = (version = window.VERDACCIO_VERSION): JSX.Element => {
return ( return (
<Right> <Right>
{POWERED_LABEL} {POWERED_LABEL}
@ -32,7 +32,7 @@ const renderRight = (version = window.VERDACCIO_VERSION) => {
); );
}; };
const renderLeft = () => ( const renderLeft = (): JSX.Element => (
<Left> <Left>
{MADEWITH_LABEL} {MADEWITH_LABEL}
<Love>{HEARTH_EMOJI}</Love> <Love>{HEARTH_EMOJI}</Love>

View File

@ -22,7 +22,7 @@ import RegistryInfoContent from '../RegistryInfoContent/RegistryInfoContent';
import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles'; import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles';
interface Props { interface Props {
logo: string; logo?: string;
username?: string; username?: string;
onLogout: () => void; onLogout: () => void;
onToggleLoginModal: () => void; onToggleLoginModal: () => void;
@ -31,7 +31,7 @@ interface Props {
} }
interface State { interface State {
anchorEl?: any; anchorEl?: null | HTMLElement | ((element: HTMLElement) => HTMLElement);
openInfoDialog: boolean; openInfoDialog: boolean;
registryUrl: string; registryUrl: string;
showMobileNavBar: boolean; showMobileNavBar: boolean;

View File

@ -1,5 +1,6 @@
import React, { MouseEvent } from 'react'; import React, { MouseEvent } from 'react';
import capitalize from 'lodash/capitalize'; import capitalize from 'lodash/capitalize';
import { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
import { Svg, Img, ImgWrapper } from './styles'; import { Svg, Img, ImgWrapper } from './styles';
@ -57,10 +58,10 @@ export interface Props {
name: keyof IconsMap; name: keyof IconsMap;
className?: string; className?: string;
onClick?: (event: MouseEvent<SVGElement | HTMLSpanElement>) => void; onClick?: (event: MouseEvent<SVGElement | HTMLSpanElement>) => void;
size?: 'sm' | 'md'; size?: Breakpoint;
pointer?: boolean; pointer?: boolean;
img?: boolean; img?: boolean;
modifiers?: any; modifiers?: null | undefined;
} }
const Icon: React.FC<Props> = ({ className, name, size = 'sm', img = false, pointer = false, ...props }) => { const Icon: React.FC<Props> = ({ className, name, size = 'sm', img = false, pointer = false, ...props }) => {

View File

@ -1,6 +1,9 @@
import styled, { css } from 'react-emotion'; import styled, { css } from 'react-emotion';
import { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
import { StyledOtherComponent } from 'create-emotion-styled';
import { DetailedHTMLProps, HTMLAttributes } from 'react';
const getSize = (size?: 'md' | 'sm') => { const getSize = (size: Breakpoint): string => {
switch (size) { switch (size) {
case 'md': case 'md':
return ` return `
@ -15,7 +18,7 @@ const getSize = (size?: 'md' | 'sm') => {
} }
}; };
const commonStyle = ({ size = 'sm', pointer, modifiers }: any) => css` const commonStyle = ({ size = 'sm' as Breakpoint, pointer, modifiers = null }): string => css`
&& { && {
display: inline-block; display: inline-block;
cursor: ${pointer ? 'pointer' : 'default'}; cursor: ${pointer ? 'pointer' : 'default'};
@ -30,7 +33,16 @@ export const Svg = styled('svg')`
} }
`; `;
export const ImgWrapper = styled('span')` export const ImgWrapper: StyledOtherComponent<
{
size?: Breakpoint;
pointer: boolean;
modifiers?: null | undefined;
name?: string | unknown;
},
DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>,
{}
> = styled('span')`
&& { && {
${commonStyle}; ${commonStyle};
} }

View File

@ -3,7 +3,7 @@ import ListItemText from '@material-ui/core/ListItemText';
import React, { Component } from 'react'; import React, { Component } from 'react';
// @ts-ignore // @ts-ignore
import { DetailContextConsumer } from '../../pages/version/Version'; import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import CopyToClipBoard from '../CopyToClipBoard'; import CopyToClipBoard from '../CopyToClipBoard';
// logos of package managers // logos of package managers
@ -14,17 +14,17 @@ import yarn from './img/yarn.svg';
import { Heading, InstallItem, PackageMangerAvatar } from './styles'; import { Heading, InstallItem, PackageMangerAvatar } from './styles';
class Install extends Component { class Install extends Component {
public render() { public render(): JSX.Element {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
{(context: any) => { {(context: Partial<VersionPageConsumerProps>) => {
return context && context.packageName && this.renderCopyCLI(context); return context && context.packageName && this.renderCopyCLI(context);
}} }}
</DetailContextConsumer> </DetailContextConsumer>
); );
} }
public renderCopyCLI = ({ packageName }: { packageName: string }) => { public renderCopyCLI = ({ packageName = '' }: Partial<VersionPageConsumerProps>) => {
return ( return (
<> <>
<List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List> <List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List>

View File

@ -6,7 +6,7 @@ interface Props {
text: string; text: string;
capitalize?: boolean; capitalize?: boolean;
weight?: string; weight?: string;
modifiers?: any; modifiers?: null | undefined;
} }
const Wrapper = styled('div')` const Wrapper = styled('div')`

View File

@ -63,7 +63,6 @@ describe('<LoginModal />', () => {
test('setCredentials - should set username and password in state', () => { test('setCredentials - should set username and password in state', () => {
const props = { const props = {
visibility: true, visibility: true,
error: {},
onCancel: () => {}, onCancel: () => {},
onSubmit: () => {}, onSubmit: () => {},
}; };
@ -80,7 +79,6 @@ describe('<LoginModal />', () => {
test('validateCredentials: should validate credentials', async () => { test('validateCredentials: should validate credentials', async () => {
const props = { const props = {
visibility: true, visibility: true,
error: {},
onCancel: () => {}, onCancel: () => {},
onSubmit: jest.fn(), onSubmit: jest.fn(),
}; };
@ -89,7 +87,7 @@ describe('<LoginModal />', () => {
const instance = wrapper.instance(); const instance = wrapper.instance();
instance.submitCredentials = jest.fn(); instance.submitCredentials = jest.fn();
const { validateCredentials, setCredentials, submitCredentials } = instance; const { handleValidateCredentials, setCredentials, submitCredentials } = instance;
expect(setCredentials('username', eventUsername)).toBeUndefined(); expect(setCredentials('username', eventUsername)).toBeUndefined();
expect(wrapper.state('form').username.value).toEqual('xyz'); expect(wrapper.state('form').username.value).toEqual('xyz');
@ -97,7 +95,7 @@ describe('<LoginModal />', () => {
expect(setCredentials('password', eventPassword)).toBeUndefined(); expect(setCredentials('password', eventPassword)).toBeUndefined();
expect(wrapper.state('form').password.value).toEqual('1234'); expect(wrapper.state('form').password.value).toEqual('1234');
validateCredentials(event); handleValidateCredentials(event);
expect(event.preventDefault).toHaveBeenCalled(); expect(event.preventDefault).toHaveBeenCalled();
expect(wrapper.state('form').username.pristine).toEqual(false); expect(wrapper.state('form').username.pristine).toEqual(false);
expect(wrapper.state('form').password.pristine).toEqual(false); expect(wrapper.state('form').password.pristine).toEqual(false);

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from '@material-ui/core/DialogTitle';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
@ -15,22 +14,35 @@ import FormHelperText from '@material-ui/core/FormHelperText';
// @ts-ignore // @ts-ignore
import classes from './login.scss'; import classes from './login.scss';
export default class LoginModal extends Component<any, any> { interface FormFields {
static propTypes = { required: boolean;
visibility: PropTypes.bool, pristine: boolean;
error: PropTypes.object, helperText: string;
onCancel: PropTypes.func, value: string;
onSubmit: PropTypes.func, }
}; export interface FormError {
type: string;
title: string;
description: string;
}
static defaultProps = { interface LoginModalProps {
error: {}, visibility: boolean;
onCancel: () => {}, error?: FormError;
onSubmit: () => {}, onCancel: () => void;
visibility: true, onSubmit: (username: string, password: string) => void;
}; }
constructor(props) { interface LoginModalState {
form: {
username: Partial<FormFields>;
password: Partial<FormFields>;
};
error?: FormError;
}
export default class LoginModal extends Component<Partial<LoginModalProps>, LoginModalState> {
constructor(props: LoginModalProps) {
super(props); super(props);
this.state = { this.state = {
form: { form: {
@ -51,11 +63,28 @@ export default class LoginModal extends Component<any, any> {
}; };
} }
public render(): JSX.Element {
const { visibility = true, onCancel = () => null, error } = this.props as LoginModalProps;
return (
<Dialog fullWidth={true} id={'login--form-container'} maxWidth={'xs'} onClose={onCancel} open={visibility}>
<form noValidate={true} onSubmit={this.handleValidateCredentials}>
<DialogTitle>{'Login'}</DialogTitle>
<DialogContent>
{error && this.renderLoginError(error)}
{this.renderNameField()}
{this.renderPasswordField()}
</DialogContent>
{this.renderActions()}
</form>
</Dialog>
);
}
/** /**
* set login modal's username and password to current state * set login modal's username and password to current state
* Required to login * Required to login
*/ */
setCredentials = (name, e) => { public setCredentials = (name, e) => {
const { form } = this.state; const { form } = this.state;
this.setState({ this.setState({
form: { form: {
@ -69,15 +98,15 @@ export default class LoginModal extends Component<any, any> {
}); });
}; };
setUsername = event => { public handleUsernameChange = event => {
this.setCredentials('username', event); this.setCredentials('username', event);
}; };
setPassword = event => { public handlePasswordChange = event => {
this.setCredentials('password', event); this.setCredentials('password', event);
}; };
validateCredentials = event => { public handleValidateCredentials = event => {
const { form } = this.state; const { form } = this.state;
// prevents default submit behavior // prevents default submit behavior
event.preventDefault(); event.preventDefault();
@ -89,7 +118,7 @@ export default class LoginModal extends Component<any, any> {
...acc, ...acc,
...{ [key]: { ...form[key], pristine: false } }, ...{ [key]: { ...form[key], pristine: false } },
}), }),
{} { username: {}, password: {} }
), ),
}, },
() => { () => {
@ -100,10 +129,14 @@ export default class LoginModal extends Component<any, any> {
); );
}; };
submitCredentials = async () => { public submitCredentials = async () => {
const { form } = this.state; const { form } = this.state;
const username = (form.username && form.username.value) || '';
const password = (form.password && form.password.value) || '';
const { onSubmit } = this.props; const { onSubmit } = this.props;
await onSubmit(form.username.value, form.password.value); if (onSubmit) {
await onSubmit(username, password);
}
// let's wait for API response and then set // let's wait for API response and then set
// username and password filed to empty state // username and password filed to empty state
this.setState({ this.setState({
@ -112,12 +145,12 @@ export default class LoginModal extends Component<any, any> {
...acc, ...acc,
...{ [key]: { ...form[key], value: '', pristine: true } }, ...{ [key]: { ...form[key], value: '', pristine: true } },
}), }),
{} { username: {}, password: {} }
), ),
}); });
}; };
renderErrorMessage(title, description) { public renderErrorMessage(title, description): JSX.Element {
return ( return (
<span> <span>
<div> <div>
@ -128,7 +161,7 @@ export default class LoginModal extends Component<any, any> {
); );
} }
renderMessage(title, description) { public renderMessage(title, description): JSX.Element {
return ( return (
<div className={classes.loginErrorMsg} id={'client-snackbar'}> <div className={classes.loginErrorMsg} id={'client-snackbar'}>
<ErrorIcon className={classes.loginIcon} /> <ErrorIcon className={classes.loginIcon} />
@ -137,37 +170,37 @@ export default class LoginModal extends Component<any, any> {
); );
} }
renderLoginError({ type, title, description }) { public renderLoginError({ type, title, description }: FormError): JSX.Element | false {
return type === 'error' && <SnackbarContent className={classes.loginError} message={this.renderMessage(title, description)} />; return type === 'error' && <SnackbarContent className={classes.loginError} message={this.renderMessage(title, description)} />;
} }
renderNameField = () => { public renderNameField = () => {
const { const {
form: { username }, form: { username },
} = this.state; } = this.state;
return ( return (
<FormControl error={!username.value && !username.pristine} fullWidth={true} required={username.required}> <FormControl error={!username.value && !username.pristine} fullWidth={true} required={username.required}>
<InputLabel htmlFor={'username'}>{'Username'}</InputLabel> <InputLabel htmlFor={'username'}>{'Username'}</InputLabel>
<Input id={'login--form-username'} onChange={this.setUsername} placeholder={'Your username'} value={username.value} /> <Input id={'login--form-username'} onChange={this.handleUsernameChange} placeholder={'Your username'} value={username.value} />
{!username.value && !username.pristine && <FormHelperText id={'username-error'}>{username.helperText}</FormHelperText>} {!username.value && !username.pristine && <FormHelperText id={'username-error'}>{username.helperText}</FormHelperText>}
</FormControl> </FormControl>
); );
}; };
renderPasswordField = () => { public renderPasswordField = () => {
const { const {
form: { password }, form: { password },
} = this.state; } = this.state;
return ( return (
<FormControl error={!password.value && !password.pristine} fullWidth={true} required={password.required} style={{ marginTop: '8px' }}> <FormControl error={!password.value && !password.pristine} fullWidth={true} required={password.required} style={{ marginTop: '8px' }}>
<InputLabel htmlFor={'password'}>{'Password'}</InputLabel> <InputLabel htmlFor={'password'}>{'Password'}</InputLabel>
<Input id={'login--form-password'} onChange={this.setPassword} placeholder={'Your strong password'} type={'password'} value={password.value} /> <Input id={'login--form-password'} onChange={this.handlePasswordChange} placeholder={'Your strong password'} type={'password'} value={password.value} />
{!password.value && !password.pristine && <FormHelperText id={'password-error'}>{password.helperText}</FormHelperText>} {!password.value && !password.pristine && <FormHelperText id={'password-error'}>{password.helperText}</FormHelperText>}
</FormControl> </FormControl>
); );
}; };
renderActions = () => { public renderActions = () => {
const { const {
form: { username, password }, form: { username, password },
} = this.state; } = this.state;
@ -183,21 +216,4 @@ export default class LoginModal extends Component<any, any> {
</DialogActions> </DialogActions>
); );
}; };
render() {
const { visibility, onCancel, error } = this.props;
return (
<Dialog fullWidth={true} id={'login--form-container'} maxWidth={'xs'} onClose={onCancel} open={visibility}>
<form noValidate={true} onSubmit={this.validateCredentials}>
<DialogTitle>{'Login'}</DialogTitle>
<DialogContent>
{this.renderLoginError(error)}
{this.renderNameField()}
{this.renderPasswordField()}
</DialogContent>
{this.renderActions()}
</form>
</Dialog>
);
}
} }

View File

@ -6,21 +6,22 @@ import { RouteComponentProps, withRouter } from 'react-router-dom';
import PackageImg from './img/package.svg'; import PackageImg from './img/package.svg';
import { Card, EmptyPackage, Heading, Inner, List, Wrapper } from './styles'; import { Card, EmptyPackage, Heading, Inner, List, Wrapper } from './styles';
import { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
export const NOT_FOUND_TEXT = "Sorry, we couldn't find it..."; export const NOT_FOUND_TEXT = "Sorry, we couldn't find it...";
export type NotFoundProps = RouteComponentProps & { width: any; history: any }; export type NotFoundProps = RouteComponentProps & { width: Breakpoint; history };
const NotFound: React.FC<NotFoundProps> = ({ history, width }) => { const NotFound: React.FC<NotFoundProps> = ({ history, width }) => {
const handleGoTo = (to: string) => () => { const handleGoTo = (to: string): (() => void | undefined) => () => {
history.push(to); history.push(to);
}; };
const handleGoBack = () => () => { const handleGoBack = (): ((e: React.MouseEvent<HTMLElement, MouseEvent>) => void | undefined) => () => {
history.goBack(); history.goBack();
}; };
const renderList = () => ( const renderList = (): JSX.Element => (
<List> <List>
<ListItem button={true} divider={true} onClick={handleGoTo('/')}> <ListItem button={true} divider={true} onClick={handleGoTo('/')}>
{'Home'} {'Home'}
@ -31,7 +32,7 @@ const NotFound: React.FC<NotFoundProps> = ({ history, width }) => {
</List> </List>
); );
const renderSubTitle = () => ( const renderSubTitle = (): JSX.Element => (
<Typography variant="subtitle1"> <Typography variant="subtitle1">
<div>{"The page you're looking for doesn't exist."}</div> <div>{"The page you're looking for doesn't exist."}</div>
<div>{'Perhaps these links will help find what you are looking for:'}</div> <div>{'Perhaps these links will help find what you are looking for:'}</div>

View File

@ -8,7 +8,7 @@ import { WrapperLink, Description, OverviewItem } from './styles';
* Generates one month back date from current time * Generates one month back date from current time
* @return {object} date object * @return {object} date object
*/ */
const dateOneMonthAgo = () => new Date(1544377770747); const dateOneMonthAgo = (): Date => new Date(1544377770747);
describe('<Package /> component', () => { describe('<Package /> component', () => {
test.skip('should load the component', () => { test.skip('should load the component', () => {

View File

@ -23,10 +23,10 @@ interface Dist {
unpackedSize: number; unpackedSize: number;
} }
interface Props { export interface PackageInterface {
name: string; name: string;
version: string; version: string;
time: string; time?: number | string;
author: Author; author: Author;
description?: string; description?: string;
keywords?: string[]; keywords?: string[];
@ -35,6 +35,7 @@ interface Props {
bugs?: Bugs; bugs?: Bugs;
dist?: Dist; dist?: Dist;
} }
// interface Props {} & PackageInterface;
import { import {
Author, Author,
@ -56,7 +57,7 @@ import {
} from './styles'; } from './styles';
import { isURL } from '../../utils/url'; import { isURL } from '../../utils/url';
const Package: React.FC<Props> = ({ const Package: React.FC<PackageInterface> = ({
author: { name: authorName, avatar: authorAvatar }, author: { name: authorName, avatar: authorAvatar },
bugs, bugs,
description, description,

View File

@ -1,25 +1,21 @@
import React, { Fragment } from 'react'; import React, { Fragment, ReactElement } from 'react';
import PropTypes from 'prop-types';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import Package from '../Package'; import Package from '../Package';
import Help from '../Help'; import Help from '../Help';
import { formatLicense } from '../../utils/package'; import { formatLicense } from '../../utils/package';
import { PackageInterface } from '../Package/Package';
// @ts-ignore // @ts-ignore
import classes from './packageList.scss'; import classes from './packageList.scss';
interface Props { interface Props {
packages: any; packages: PackageInterface[];
} }
export default class PackageList extends React.Component<Props, {}> { export default class PackageList extends React.Component<Props, {}> {
static propTypes = { public render(): ReactElement<HTMLElement> {
packages: PropTypes.array,
};
render() {
return ( return (
<div className={'package-list-items'}> <div className={'package-list-items'}>
<div className={classes.pkgContainer}>{this.hasPackages() ? this.renderPackages() : <Help />}</div> <div className={classes.pkgContainer}>{this.hasPackages() ? this.renderPackages() : <Help />}</div>
@ -27,20 +23,19 @@ export default class PackageList extends React.Component<Props, {}> {
); );
} }
hasPackages() { private hasPackages(): boolean {
const { packages } = this.props; const { packages } = this.props;
return packages.length > 0; return packages.length > 0;
} }
renderPackages = () => { private renderPackages = () => {
return <>{this.renderList()}</>; return <>{this.renderList()}</>;
}; };
renderList = () => { private renderList = () => {
const { packages } = this.props; const { packages } = this.props;
return packages.map((pkg, i) => { return packages.map((pkg, i) => {
const { name, version, description, time, keywords, dist, homepage, bugs } = pkg; const { name, version, description, time, keywords, dist, homepage, bugs, author } = pkg;
const author = pkg.author;
// TODO: move format license to API side. // TODO: move format license to API side.
const license = formatLicense(pkg.license); const license = formatLicense(pkg.license);
return ( return (

View File

@ -11,7 +11,7 @@ import { getCLISetRegistry, getCLIChangePassword, getCLISetConfigRegistry } from
import { NODE_MANAGER } from '../../utils/constants'; import { NODE_MANAGER } from '../../utils/constants';
/* eslint react/prop-types:0 */ /* eslint react/prop-types:0 */
function TabContainer({ children }) { function TabContainer({ children }): JSX.Element {
return ( return (
<CommandContainer> <CommandContainer>
<Typography component="div" style={{ padding: 0, minHeight: 170 }}> <Typography component="div" style={{ padding: 0, minHeight: 170 }}>
@ -22,15 +22,20 @@ function TabContainer({ children }) {
} }
class RegistryInfoContent extends Component<Props, State> { class RegistryInfoContent extends Component<Props, State> {
state = { public state = {
tabPosition: 0, tabPosition: 0,
}; };
render() { public render(): JSX.Element {
return <div>{this.renderTabs()}</div>; return <div>{this.renderTabs()}</div>;
} }
renderTabs() { private handleChange = (event: React.ChangeEvent<{}>, tabPosition: number) => {
event.preventDefault();
this.setState({ tabPosition });
};
private renderTabs(): JSX.Element {
const { scope, registryUrl } = this.props; const { scope, registryUrl } = this.props;
const { tabPosition } = this.state; const { tabPosition } = this.state;
@ -48,7 +53,7 @@ class RegistryInfoContent extends Component<Props, State> {
); );
} }
renderNpmTab(scope: string, registryUrl: string) { private renderNpmTab(scope: string, registryUrl: string): JSX.Element {
return ( return (
<React.Fragment> <React.Fragment>
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)} /> <CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)} />
@ -58,7 +63,7 @@ class RegistryInfoContent extends Component<Props, State> {
); );
} }
renderPNpmTab(scope: string, registryUrl: string) { private renderPNpmTab(scope: string, registryUrl: string): JSX.Element {
return ( return (
<React.Fragment> <React.Fragment>
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)} /> <CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)} />
@ -68,18 +73,13 @@ class RegistryInfoContent extends Component<Props, State> {
); );
} }
renderYarnTab(scope: string, registryUrl: string) { private renderYarnTab(scope: string, registryUrl: string): JSX.Element {
return ( return (
<React.Fragment> <React.Fragment>
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} /> <CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} />
</React.Fragment> </React.Fragment>
); );
} }
handleChange = (event: any, tabPosition: number) => {
event.preventDefault();
this.setState({ tabPosition });
};
} }
export default RegistryInfoContent; export default RegistryInfoContent;

View File

@ -8,7 +8,7 @@ import { Props } from './types';
const LABEL = 'CLOSE'; const LABEL = 'CLOSE';
const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }): any => ( const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }) => (
<Dialog id="registryInfo--dialog-container" onClose={onClose} open={open}> <Dialog id="registryInfo--dialog-container" onClose={onClose} open={open}>
<Title disableTypography={true}>{'Register Info'}</Title> <Title disableTypography={true}>{'Register Info'}</Title>
<Content>{children}</Content> <Content>{children}</Content>

View File

@ -13,7 +13,7 @@ import { Heading, GithubLink, RepositoryListItem } from './styles';
import git from './img/git.png'; import git from './img/git.png';
import { isURL } from '../../utils/url'; import { isURL } from '../../utils/url';
class Repository extends Component<any, any> { class Repository extends Component {
public render(): ReactElement<HTMLElement> { public render(): ReactElement<HTMLElement> {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
@ -33,12 +33,7 @@ class Repository extends Component<any, any> {
} }
private renderRepository = packageMeta => { private renderRepository = packageMeta => {
const { const { repository: { url = null } = {} } = packageMeta.latest;
repository: {
// @ts-ignore
url,
} = {},
} = packageMeta.latest;
if (!url || isURL(url) === false) { if (!url || isURL(url) === false) {
return null; return null;

View File

@ -154,7 +154,7 @@ describe('<Search /> component test', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
jest.doMock('lodash/debounce', () => { jest.doMock('lodash/debounce', () => {
return function debounceMock(fn, delay) { return function debounceMock(fn) {
return fn; return fn;
}; };
}); });

View File

@ -11,11 +11,15 @@ import colors from '../../utils/styles/colors';
export interface State { export interface State {
search: string; search: string;
suggestions: any[]; suggestions: unknown[];
loading: boolean; loading: boolean;
loaded: boolean; loaded: boolean;
error: boolean; error: boolean;
} }
interface AbortControllerInterface {
signal: () => void;
abort: () => void;
}
export type cancelAllSearchRequests = () => void; export type cancelAllSearchRequests = () => void;
export type handlePackagesClearRequested = () => void; export type handlePackagesClearRequested = () => void;
@ -31,8 +35,6 @@ const CONSTANTS = {
}; };
export class Search extends Component<RouteComponentProps<{}>, State> { export class Search extends Component<RouteComponentProps<{}>, State> {
private requestList: any[];
constructor(props: RouteComponentProps<{}>) { constructor(props: RouteComponentProps<{}>) {
super(props); super(props);
this.state = { this.state = {
@ -96,7 +98,10 @@ export class Search extends Component<RouteComponentProps<{}>, State> {
/** /**
* When an user select any package by clicking or pressing return key. * When an user select any package by clicking or pressing return key.
*/ */
private handleClickSearch: handleClickSearch = (event, { suggestionValue, method }: any) => { private handleClickSearch = (
event: React.KeyboardEvent<HTMLInputElement>,
{ suggestionValue, method }: { suggestionValue: string[]; method: string }
): void | undefined => {
const { history } = this.props; const { history } = this.props;
// stops event bubbling // stops event bubbling
event.stopPropagation(); event.stopPropagation();
@ -163,7 +168,9 @@ export class Search extends Component<RouteComponentProps<{}>, State> {
); );
} }
public getAdorment(): ReactElement<HTMLElement> { private requestList: AbortControllerInterface[];
public getAdorment(): JSX.Element {
return ( return (
<InputAdornment position={'start'} style={{ color: colors.white }}> <InputAdornment position={'start'} style={{ color: colors.white }}>
<IconSearch /> <IconSearch />

View File

@ -1,5 +1,5 @@
import CircularProgress from '@material-ui/core/CircularProgress'; import CircularProgress from '@material-ui/core/CircularProgress';
import styled, { css, Themed } from 'react-emotion'; import styled, { css } from 'react-emotion';
import colors from '../../utils/styles/colors'; import colors from '../../utils/styles/colors';
@ -8,7 +8,7 @@ export const Wrapper = styled('div')`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
${(props): Themed<any, object> => ${props =>
// @ts-ignore // @ts-ignore
props.centered && props.centered &&
css` css`

View File

@ -8,7 +8,7 @@ import { formatDateDistance } from '../../utils/package';
import { Heading, Spacer, ListItemText } from './styles'; import { Heading, Spacer, ListItemText } from './styles';
class UpLinks extends React.PureComponent<any> { class UpLinks extends React.PureComponent<{}> {
public render(): ReactElement<HTMLElement> { public render(): ReactElement<HTMLElement> {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>

View File

@ -8,7 +8,7 @@ import { DIST_TAGS } from '../../../lib/constants';
const NOT_AVAILABLE = 'Not available'; const NOT_AVAILABLE = 'Not available';
class Versions extends React.PureComponent<any> { class Versions extends React.PureComponent {
public render(): ReactElement<HTMLDivElement> { public render(): ReactElement<HTMLDivElement> {
return ( return (
<DetailContextConsumer> <DetailContextConsumer>
@ -19,7 +19,7 @@ class Versions extends React.PureComponent<any> {
); );
} }
public renderPackageList = (packages: any, isVersion: boolean = false, timeMap: Record<string, any> = {}): ReactElement<HTMLDivElement> => { public renderPackageList = (packages: {}, isVersion: boolean = false, timeMap: Record<string, {}> = {}): ReactElement<HTMLDivElement> => {
return ( return (
<List> <List>
{Object.keys(packages) {Object.keys(packages)

View File

@ -8,7 +8,7 @@ import App from './App';
const rootNode = document.getElementById('root'); const rootNode = document.getElementById('root');
const renderApp = Component => { const renderApp = (Component): void => {
ReactDOM.render( ReactDOM.render(
<AppContainer> <AppContainer>
<Component /> <Component />

View File

@ -4,7 +4,7 @@ import PackageList from '../../components/PackageList';
interface Props { interface Props {
isUserLoggedIn: boolean; isUserLoggedIn: boolean;
packages: any[]; packages: [];
} }
const Home: React.FC<Props> = ({ packages }) => ( const Home: React.FC<Props> = ({ packages }) => (

View File

@ -3,31 +3,44 @@ import Grid from '@material-ui/core/Grid';
import Loading from '../../components/Loading/Loading'; import Loading from '../../components/Loading/Loading';
import DetailContainer from '../../components/DetailContainer/DetailContainer'; import DetailContainer from '../../components/DetailContainer/DetailContainer';
import DetailSidebar from '../../components/DetailSidebar/DetailSidebar'; import DetailSidebar from '../../components/DetailSidebar/DetailSidebar';
import { callDetailPage, DetailPage } from '../../utils/calls'; import { callDetailPage } from '../../utils/calls';
import { getRouterPackageName } from '../../utils/package'; import { getRouterPackageName } from '../../utils/package';
import NotFound from '../../components/NotFound'; import NotFound from '../../components/NotFound';
import { PackageMetaInterface } from '../../../types/packageMeta';
export interface DetailContextProps { export interface DetailContextProps {
packageMeta: any; packageMeta: PackageMetaInterface;
readMe: any; readMe: string;
packageName: string; packageName: string;
enableLoading: () => void; enableLoading: () => void;
} }
export const DetailContext = React.createContext<DetailContextProps | null>(null); export const DetailContext = React.createContext<Partial<DetailContextProps>>({});
export interface VersionPageConsumerProps { export interface VersionPageConsumerProps {
packageMeta: any; packageMeta: PackageMetaInterface;
readMe: any; readMe: string;
packageName: any; packageName: string;
enableLoading: any; enableLoading: () => void;
} }
export const DetailContextProvider: Provider<VersionPageConsumerProps | null> = DetailContext.Provider; export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> = DetailContext.Provider;
export const DetailContextConsumer: Consumer<VersionPageConsumerProps | null> = DetailContext.Consumer; export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> = DetailContext.Consumer;
class VersionPage extends Component<any, any> { interface PropsInterface {
constructor(props: any) { match: boolean;
}
interface StateInterface {
readMe: string;
packageName: string;
packageMeta: PackageMetaInterface | null;
isLoading: boolean;
notFound: boolean;
}
class VersionPage extends Component<PropsInterface, Partial<StateInterface>> {
constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -39,7 +52,7 @@ class VersionPage extends Component<any, any> {
}; };
} }
public static getDerivedStateFromProps(nextProps: any, prevState: any): any { public static getDerivedStateFromProps(nextProps, prevState): { packageName?: string; isLoading: boolean; notFound?: boolean } | null {
const { match } = nextProps; const { match } = nextProps;
const packageName = getRouterPackageName(match); const packageName = getRouterPackageName(match);
@ -65,10 +78,10 @@ class VersionPage extends Component<any, any> {
} }
/* eslint no-unused-vars: 0 */ /* eslint no-unused-vars: 0 */
public async componentDidUpdate(nextProps: any, prevState: any): Promise<void> { public async componentDidUpdate(nextProps, prevState: StateInterface): Promise<void> {
const { packageName } = this.state; const { packageName } = this.state;
if (packageName !== prevState.packageName) { if (packageName !== prevState.packageName) {
const { readMe, packageMeta } = await callDetailPage(packageName); const { readMe, packageMeta } = (await callDetailPage(packageName)) as Partial<StateInterface>;
this.setState({ this.setState({
readMe, readMe,
packageMeta, packageMeta,
@ -112,7 +125,7 @@ class VersionPage extends Component<any, any> {
}); });
try { try {
const { readMe, packageMeta } = await callDetailPage(packageName); const { readMe, packageMeta } = (await callDetailPage(packageName)) as Partial<StateInterface>;
this.setState({ this.setState({
readMe, readMe,
packageMeta, packageMeta,

View File

@ -2,7 +2,7 @@
import React, { Component, ReactElement } from 'react'; import React, { Component, ReactElement } from 'react';
import { Router, Route, Switch } from 'react-router-dom'; import { Router, Route, Switch } from 'react-router-dom';
import { AppContextConsumer } from './App/App'; import { AppContextConsumer, AppStateInterface } from './App/App';
import { asyncComponent } from './utils/asyncComponent'; import { asyncComponent } from './utils/asyncComponent';
import history from './history'; import history from './history';
@ -12,7 +12,12 @@ const NotFound = asyncComponent(() => import('./components/NotFound'));
const VersionPackage = asyncComponent(() => import('./pages/version/Version')); const VersionPackage = asyncComponent(() => import('./pages/version/Version'));
const HomePage = asyncComponent(() => import('./pages/home')); const HomePage = asyncComponent(() => import('./pages/home'));
class RouterApp extends Component<any, any> { interface RouterAppProps {
onLogout: () => void;
onToggleLoginModal: () => void;
}
class RouterApp extends Component<RouterAppProps> {
public render(): ReactElement<HTMLDivElement> { public render(): ReactElement<HTMLDivElement> {
return ( return (
<Router history={history}> <Router history={history}>
@ -34,8 +39,8 @@ class RouterApp extends Component<any, any> {
return ( return (
<AppContextConsumer> <AppContextConsumer>
{function renderConsumerVersionPage({ logoUrl, scope, user }: any) { {function renderConsumerVersionPage({ logoUrl, scope = '', user }: Partial<AppStateInterface>) {
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user.username} />; return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user && user.username} />;
}} }}
</AppContextConsumer> </AppContextConsumer>
); );
@ -44,7 +49,7 @@ class RouterApp extends Component<any, any> {
public renderHomePage = (): ReactElement<HTMLDivElement> => { public renderHomePage = (): ReactElement<HTMLDivElement> => {
return ( return (
<AppContextConsumer> <AppContextConsumer>
{function renderConsumerVersionPage({ isUserLoggedIn, packages }: any) { {function renderConsumerVersionPage({ isUserLoggedIn, packages }: Partial<AppStateInterface>) {
// @ts-ignore // @ts-ignore
return <HomePage isUserLoggedIn={isUserLoggedIn} packages={packages} />; return <HomePage isUserLoggedIn={isUserLoggedIn} packages={packages} />;
}} }}
@ -52,10 +57,10 @@ class RouterApp extends Component<any, any> {
); );
}; };
public renderVersionPage = (routerProps: any): ReactElement<HTMLDivElement> => { public renderVersionPage = (routerProps): ReactElement<HTMLDivElement> => {
return ( return (
<AppContextConsumer> <AppContextConsumer>
{function renderConsumerVersionPage({ isUserLoggedIn }: any) { {function renderConsumerVersionPage({ isUserLoggedIn }: Partial<AppStateInterface>) {
return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />; return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />;
}} }}
</AppContextConsumer> </AppContextConsumer>

View File

@ -6,9 +6,9 @@ import '../../types';
* @param {object} response * @param {object} response
* @returns {promise} * @returns {promise}
*/ */
function handleResponseType(response): Promise<any> { function handleResponseType(response: Response): Promise<[boolean, Blob | string]> | Promise<void> {
if (response.headers) { if (response.headers) {
const contentType = response.headers.get('Content-Type'); const contentType = response.headers.get('Content-Type') as string;
if (contentType.includes('application/pdf')) { if (contentType.includes('application/pdf')) {
return Promise.all([response.ok, response.blob()]); return Promise.all([response.ok, response.blob()]);
} }
@ -25,16 +25,16 @@ function handleResponseType(response): Promise<any> {
} }
class API { class API {
public request(url: string, method = 'GET', options: any = {}): Promise<any> { public request<T>(url: string, method = 'GET', options?: RequestInit): Promise<T> {
if (!window.VERDACCIO_API_URL) { if (!window.VERDACCIO_API_URL) {
throw new Error('VERDACCIO_API_URL is not defined!'); throw new Error('VERDACCIO_API_URL is not defined!');
} }
const token = storage.getItem('token'); const token = storage.getItem('token');
if (token) { const headers = new Headers(options && options.headers);
if (!options.headers) options.headers = {}; if (token && options && options.headers) {
headers.set('Authorization', `Bearer ${token}`);
options.headers.authorization = `Bearer ${token}`; options.headers = Object.assign(options.headers, headers);
} }
if (!['http://', 'https://', '//'].some(prefix => url.startsWith(prefix))) { if (!['http://', 'https://', '//'].some(prefix => url.startsWith(prefix))) {
@ -42,7 +42,7 @@ class API {
url = window.VERDACCIO_API_URL + url; url = window.VERDACCIO_API_URL + url;
} }
return new Promise<any>((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(url, { fetch(url, {
method, method,
credentials: 'same-origin', credentials: 'same-origin',

View File

@ -1,11 +1,11 @@
import React from 'react'; import React, { ComponentClass } from 'react';
export function asyncComponent(getComponent) { export function asyncComponent(getComponent): ComponentClass {
return class AsyncComponent extends React.Component { return class AsyncComponent extends React.Component {
static Component = null; public static Component = null;
state = { Component: AsyncComponent.Component }; public state = { Component: AsyncComponent.Component };
componentDidMount() { public componentDidMount(): void {
const { Component } = this.state; const { Component } = this.state;
if (!Component) { if (!Component) {
getComponent() getComponent()
@ -19,7 +19,8 @@ export function asyncComponent(getComponent) {
}); });
} }
} }
render() {
public render(): JSX.Element | null {
const { Component } = this.state; const { Component } = this.state;
if (Component) { if (Component) {
// eslint-disable-next-line verdaccio/jsx-spread // eslint-disable-next-line verdaccio/jsx-spread

View File

@ -1,8 +1,9 @@
import API from './api'; import API from './api';
import { PackageMetaInterface } from 'types/packageMeta';
export interface DetailPage { export interface DetailPage {
readMe: any; readMe: string | {};
packageMeta: any; packageMeta: PackageMetaInterface | {};
} }
export async function callDetailPage(packageName): Promise<DetailPage> { export async function callDetailPage(packageName): Promise<DetailPage> {

View File

@ -1,6 +1,6 @@
import { SyntheticEvent } from 'react'; import { SyntheticEvent } from 'react';
export const copyToClipBoardUtility = (str: string): any => (event: SyntheticEvent<HTMLElement>): void => { export const copyToClipBoardUtility = (str: string): ((e: SyntheticEvent<HTMLElement>) => void) => (event: SyntheticEvent<HTMLElement>): void => {
event.preventDefault(); event.preventDefault();
const node = document.createElement('div'); const node = document.createElement('div');

View File

@ -1,3 +1,3 @@
export default function fileSizeSI(a?: any, b?: any, c?: any, d?: any, e?: any) { export default function fileSizeSI(a: number, b?: typeof Math, c?: (p: number) => number, d?: number, e?: number): string {
return ((b = Math), (c = b.log), (d = 1e3), (e = (c(a) / c(d)) | 0), a / b.pow(d, e)).toFixed(2) + ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes'); return ((b = Math), (c = b.log), (d = 1e3), (e = (c(a) / c(d)) | 0), a / b.pow(d, e)).toFixed(2) + ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes');
} }

View File

@ -5,30 +5,34 @@ import { Base64 } from 'js-base64';
import API from './api'; import API from './api';
import { HEADERS } from '../../lib/constants'; import { HEADERS } from '../../lib/constants';
export function isTokenExpire(token?: any) { interface PayloadInterface {
exp: number;
}
export function isTokenExpire(token?: string): boolean {
if (!isString(token)) { if (!isString(token)) {
return true; return true;
} }
let [, payload]: any = token.split('.'); const [, payload] = token.split('.');
if (!payload) { if (!payload) {
return true; return true;
} }
let exp: number;
try { try {
payload = JSON.parse(Base64.decode(payload)); exp = JSON.parse(Base64.decode(payload)).exp;
} catch (error) { } catch (error) {
// eslint-disable-next-line console.error('Invalid token:', error, token);
console.error('Invalid token:', error, token);
return true; return true;
} }
if (!payload.exp || !isNumber(payload.exp)) { if (!exp || !isNumber(exp)) {
return true; return true;
} }
// Report as expire before (real expire time - 30s) // Report as expire before (real expire time - 30s)
const jsTimestamp = payload.exp * 1000 - 30000; const jsTimestamp = exp * 1000 - 30000;
const expired = Date.now() >= jsTimestamp; const expired = Date.now() >= jsTimestamp;
return expired; return expired;

View File

@ -1,6 +1,6 @@
import parseXSS from 'xss'; import parseXSS from 'xss';
export function preventXSS(text: string) { export function preventXSS(text: string): string {
const encodedText = parseXSS.filterXSS(text); const encodedText = parseXSS.filterXSS(text);
return encodedText; return encodedText;

View File

@ -1,7 +1,7 @@
/** /**
* CSS to represent truncated text with an ellipsis. * CSS to represent truncated text with an ellipsis.
*/ */
export function ellipsis(width: string | number) { export function ellipsis(width: string | number): {} {
return { return {
display: 'inline-block', display: 'inline-block',
maxWidth: width, maxWidth: width,
@ -24,7 +24,7 @@ interface SpacingShortHand<type> {
const positionMap = ['Top', 'Right', 'Bottom', 'Left']; const positionMap = ['Top', 'Right', 'Bottom', 'Left'];
export function spacing(property: 'padding' | 'margin', ...values: SpacingShortHand<number | string>[]) { export function spacing(property: 'padding' | 'margin', ...values: SpacingShortHand<number | string>[]): {} {
const [firstValue = 0, secondValue = 0, thirdValue = 0, fourthValue = 0] = values; const [firstValue = 0, secondValue = 0, thirdValue = 0, fourthValue = 0] = values;
const valuesWithDefaults = [firstValue, secondValue, thirdValue, fourthValue]; const valuesWithDefaults = [firstValue, secondValue, thirdValue, fourthValue];
let styles = {}; let styles = {};

7
types/custom.d.ts vendored
View File

@ -1,10 +1,7 @@
// https://stackoverflow.com/questions/44717164/unable-to-import-svg-files-in-typescript // https://stackoverflow.com/questions/44717164/unable-to-import-svg-files-in-typescript
declare module '*.svg' { declare module '*.svg' {
const content: any; const content: string;
export default content; export default content;
} }
declare module '*.png' { declare module '*.png';
const content: any;
export default content;
}

11
types/packageMeta.ts Normal file
View File

@ -0,0 +1,11 @@
export interface PackageMetaInterface {
latest: {
name: string;
dist: {
fileCount: number;
unpackedSize: number;
};
license: string;
};
_uplinks: {};
}