1
0
mirror of https://github.com/SomboChea/ui synced 2024-12-25 05:34:26 +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
*/
class API {
request() {
return register.call(null, ...arguments);
public request(...rest) {
return register.call(null, ...rest);
}
}

View File

@ -2,6 +2,6 @@
* Mock response for logo api
* @returns {promise}
*/
export default function() {
export default function<T>(): Promise<T> {
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', () => {
class LocalStorageMock {
store: object;
private store: object;
public constructor() {
this.store = {};
}
@ -43,7 +43,7 @@ describe('App', () => {
expect(wrapper.state().showLoginModal).toBeFalsy();
handleToggleLoginModal();
expect(wrapper.state('showLoginModal')).toBeTruthy();
expect(wrapper.state('error')).toEqual({});
expect(wrapper.state('error')).toEqual(undefined);
});
test('isUserAlreadyLoggedIn: token already available in storage', async () => {

View File

@ -14,14 +14,26 @@ import '../styles/typeface-roboto.scss';
import '../styles/main.scss';
import 'normalize.css';
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 AppContextConsumer = AppContext.Consumer;
export default class App extends Component<any, any> {
public state = {
error: {},
export interface AppStateInterface {
error?: FormError;
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
logoUrl: window.VERDACCIO_LOGO,
user: {},
@ -49,7 +61,7 @@ export default class App extends Component<any, any> {
public render(): React.ReactElement<HTMLDivElement> {
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 (
// @ts-ignore
@ -112,7 +124,6 @@ export default class App extends Component<any, any> {
this.setState(prevState => ({
// @ts-ignore
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> {
return (
<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 (
<a href={link} target={'_blank'}>
{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 List from '@material-ui/core/List';
@ -8,18 +8,18 @@ import { DetailContextConsumer } from '../../pages/version/Version';
import { Heading, AuthorListItem } from './styles';
import { isEmail } from '../../utils/url';
class Authors extends Component<any, any> {
render() {
class Authors extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{(context: any) => {
{context => {
return context && context.packageMeta && this.renderAuthor(context.packageMeta);
}}
</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) {
return avatarComponent;
}
@ -31,7 +31,7 @@ class Authors extends Component<any, any> {
);
}
renderAuthor = packageMeta => {
public renderAuthor = packageMeta => {
const { author, name: packageName, version } = packageMeta.latest;
if (!author) {

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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 { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
@ -7,8 +7,19 @@ import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/ver
import { CardWrap, Heading, Tags, Tag } from './styles';
import NoItems from '../NoItems';
class DepDetail extends Component<any, any> {
constructor(props: any) {
interface DepDetailProps {
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);
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> {
const { dependencies, title } = this.props;
const deps = Object.entries(dependencies);
const deps = Object.entries(dependencies) as [];
return (
<DetailContextConsumer>
{({ enableLoading }: any) => {
{({ enableLoading }) => {
return (
<CardWrap>
<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 => {
const [name, version] = dep;
const [name, version] = dep as [string, string];
return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />;
});
}
class Dependencies extends Component<any, any> {
class Dependencies extends Component {
public state = {
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;
}

View File

@ -14,7 +14,7 @@ interface DetailContainerState {
tabPosition: number;
}
class DetailContainer extends Component<any, DetailContainerState> {
class DetailContainer<P> extends Component<P, DetailContainerState> {
public state = {
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();
this.setState({ tabPosition });
};

View File

@ -11,18 +11,21 @@ import { isEmail } from '../../utils/url';
interface Props {
type: 'contributors' | 'maintainers';
}
interface State {
visibleDevs: number;
}
class Developers extends Component<Props, any> {
state = {
class Developers extends Component<Props, State> {
public state = {
visibleDevs: 6,
};
render() {
public render(): JSX.Element {
return (
<DetailContextConsumer>
{({ packageMeta }: any) => {
{({ packageMeta }) => {
const { type } = this.props;
const developerType = packageMeta.latest[type];
const developerType = packageMeta && packageMeta.latest[type];
if (!developerType || developerType.length === 0) return null;
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 }));
};
renderDevelopers = (developers, packageMeta) => {
private renderDevelopers = (developers, packageMeta) => {
const { type } = this.props;
const { visibleDevs } = this.state;
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) {
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 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 { DetailContextConsumer } from '../../pages/version/Version';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { Heading, DistListItem, DistChips } from './styles';
import fileSizeSI from '../../utils/file-size';
import { PackageMetaInterface } from 'types/packageMeta';
class Dist extends Component<any, any> {
render() {
class Dist extends Component {
public render(): JSX.Element {
return (
<DetailContextConsumer>
{(context: any) => {
return this.renderDist(context);
{(context: Partial<VersionPageConsumerProps>) => {
return context && context.packageMeta && this.renderDist(context.packageMeta);
}}
</DetailContextConsumer>
);
}
renderChips(dist: any, license: string) {
private renderChips(dist, license: string): JSX.Element | never[] {
const distDict = {
'file-count': dist.fileCount,
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
@ -43,8 +44,8 @@ class Dist extends Component<any, any> {
return chipsList;
}
renderDist = ({ packageMeta }: any) => {
const { dist = {}, license } = packageMeta.latest;
private renderDist = (packageMeta: PackageMetaInterface) => {
const { dist, license } = packageMeta && packageMeta.latest;
return (
<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 { goToVerdaccioWebsite } from '../../utils/windows';
const renderTooltip = () => (
const renderTooltip = (): JSX.Element => (
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
@ -22,7 +22,7 @@ const ON_LABEL = 'on';
const HEARTH_EMOJI = '♥';
// @ts-ignore
const renderRight = (version = window.VERDACCIO_VERSION) => {
const renderRight = (version = window.VERDACCIO_VERSION): JSX.Element => {
return (
<Right>
{POWERED_LABEL}
@ -32,7 +32,7 @@ const renderRight = (version = window.VERDACCIO_VERSION) => {
);
};
const renderLeft = () => (
const renderLeft = (): JSX.Element => (
<Left>
{MADEWITH_LABEL}
<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';
interface Props {
logo: string;
logo?: string;
username?: string;
onLogout: () => void;
onToggleLoginModal: () => void;
@ -31,7 +31,7 @@ interface Props {
}
interface State {
anchorEl?: any;
anchorEl?: null | HTMLElement | ((element: HTMLElement) => HTMLElement);
openInfoDialog: boolean;
registryUrl: string;
showMobileNavBar: boolean;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle';
import Dialog from '@material-ui/core/Dialog';
@ -15,22 +14,35 @@ import FormHelperText from '@material-ui/core/FormHelperText';
// @ts-ignore
import classes from './login.scss';
export default class LoginModal extends Component<any, any> {
static propTypes = {
visibility: PropTypes.bool,
error: PropTypes.object,
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
};
interface FormFields {
required: boolean;
pristine: boolean;
helperText: string;
value: string;
}
export interface FormError {
type: string;
title: string;
description: string;
}
static defaultProps = {
error: {},
onCancel: () => {},
onSubmit: () => {},
visibility: true,
};
interface LoginModalProps {
visibility: boolean;
error?: FormError;
onCancel: () => void;
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);
this.state = {
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
* Required to login
*/
setCredentials = (name, e) => {
public setCredentials = (name, e) => {
const { form } = this.state;
this.setState({
form: {
@ -69,15 +98,15 @@ export default class LoginModal extends Component<any, any> {
});
};
setUsername = event => {
public handleUsernameChange = event => {
this.setCredentials('username', event);
};
setPassword = event => {
public handlePasswordChange = event => {
this.setCredentials('password', event);
};
validateCredentials = event => {
public handleValidateCredentials = event => {
const { form } = this.state;
// prevents default submit behavior
event.preventDefault();
@ -89,7 +118,7 @@ export default class LoginModal extends Component<any, any> {
...acc,
...{ [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 username = (form.username && form.username.value) || '';
const password = (form.password && form.password.value) || '';
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
// username and password filed to empty state
this.setState({
@ -112,12 +145,12 @@ export default class LoginModal extends Component<any, any> {
...acc,
...{ [key]: { ...form[key], value: '', pristine: true } },
}),
{}
{ username: {}, password: {} }
),
});
};
renderErrorMessage(title, description) {
public renderErrorMessage(title, description): JSX.Element {
return (
<span>
<div>
@ -128,7 +161,7 @@ export default class LoginModal extends Component<any, any> {
);
}
renderMessage(title, description) {
public renderMessage(title, description): JSX.Element {
return (
<div className={classes.loginErrorMsg} id={'client-snackbar'}>
<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)} />;
}
renderNameField = () => {
public renderNameField = () => {
const {
form: { username },
} = this.state;
return (
<FormControl error={!username.value && !username.pristine} fullWidth={true} required={username.required}>
<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>}
</FormControl>
);
};
renderPasswordField = () => {
public renderPasswordField = () => {
const {
form: { password },
} = this.state;
return (
<FormControl error={!password.value && !password.pristine} fullWidth={true} required={password.required} style={{ marginTop: '8px' }}>
<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>}
</FormControl>
);
};
renderActions = () => {
public renderActions = () => {
const {
form: { username, password },
} = this.state;
@ -183,21 +216,4 @@ export default class LoginModal extends Component<any, any> {
</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 { 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 type NotFoundProps = RouteComponentProps & { width: any; history: any };
export type NotFoundProps = RouteComponentProps & { width: Breakpoint; history };
const NotFound: React.FC<NotFoundProps> = ({ history, width }) => {
const handleGoTo = (to: string) => () => {
const handleGoTo = (to: string): (() => void | undefined) => () => {
history.push(to);
};
const handleGoBack = () => () => {
const handleGoBack = (): ((e: React.MouseEvent<HTMLElement, MouseEvent>) => void | undefined) => () => {
history.goBack();
};
const renderList = () => (
const renderList = (): JSX.Element => (
<List>
<ListItem button={true} divider={true} onClick={handleGoTo('/')}>
{'Home'}
@ -31,7 +32,7 @@ const NotFound: React.FC<NotFoundProps> = ({ history, width }) => {
</List>
);
const renderSubTitle = () => (
const renderSubTitle = (): JSX.Element => (
<Typography variant="subtitle1">
<div>{"The page you're looking for doesn't exist."}</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
* @return {object} date object
*/
const dateOneMonthAgo = () => new Date(1544377770747);
const dateOneMonthAgo = (): Date => new Date(1544377770747);
describe('<Package /> component', () => {
test.skip('should load the component', () => {

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { getCLISetRegistry, getCLIChangePassword, getCLISetConfigRegistry } from
import { NODE_MANAGER } from '../../utils/constants';
/* eslint react/prop-types:0 */
function TabContainer({ children }) {
function TabContainer({ children }): JSX.Element {
return (
<CommandContainer>
<Typography component="div" style={{ padding: 0, minHeight: 170 }}>
@ -22,15 +22,20 @@ function TabContainer({ children }) {
}
class RegistryInfoContent extends Component<Props, State> {
state = {
public state = {
tabPosition: 0,
};
render() {
public render(): JSX.Element {
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 { 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 (
<React.Fragment>
<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 (
<React.Fragment>
<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 (
<React.Fragment>
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} />
</React.Fragment>
);
}
handleChange = (event: any, tabPosition: number) => {
event.preventDefault();
this.setState({ tabPosition });
};
}
export default RegistryInfoContent;

View File

@ -8,7 +8,7 @@ import { Props } from './types';
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}>
<Title disableTypography={true}>{'Register Info'}</Title>
<Content>{children}</Content>

View File

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

View File

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

View File

@ -11,11 +11,15 @@ import colors from '../../utils/styles/colors';
export interface State {
search: string;
suggestions: any[];
suggestions: unknown[];
loading: boolean;
loaded: boolean;
error: boolean;
}
interface AbortControllerInterface {
signal: () => void;
abort: () => void;
}
export type cancelAllSearchRequests = () => void;
export type handlePackagesClearRequested = () => void;
@ -31,8 +35,6 @@ const CONSTANTS = {
};
export class Search extends Component<RouteComponentProps<{}>, State> {
private requestList: any[];
constructor(props: RouteComponentProps<{}>) {
super(props);
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.
*/
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;
// stops event bubbling
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 (
<InputAdornment position={'start'} style={{ color: colors.white }}>
<IconSearch />

View File

@ -1,5 +1,5 @@
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';
@ -8,7 +8,7 @@ export const Wrapper = styled('div')`
display: flex;
align-items: center;
justify-content: center;
${(props): Themed<any, object> =>
${props =>
// @ts-ignore
props.centered &&
css`

View File

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

View File

@ -8,7 +8,7 @@ import { DIST_TAGS } from '../../../lib/constants';
const NOT_AVAILABLE = 'Not available';
class Versions extends React.PureComponent<any> {
class Versions extends React.PureComponent {
public render(): ReactElement<HTMLDivElement> {
return (
<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 (
<List>
{Object.keys(packages)

View File

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

View File

@ -4,7 +4,7 @@ import PackageList from '../../components/PackageList';
interface Props {
isUserLoggedIn: boolean;
packages: any[];
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 DetailContainer from '../../components/DetailContainer/DetailContainer';
import DetailSidebar from '../../components/DetailSidebar/DetailSidebar';
import { callDetailPage, DetailPage } from '../../utils/calls';
import { callDetailPage } from '../../utils/calls';
import { getRouterPackageName } from '../../utils/package';
import NotFound from '../../components/NotFound';
import { PackageMetaInterface } from '../../../types/packageMeta';
export interface DetailContextProps {
packageMeta: any;
readMe: any;
packageMeta: PackageMetaInterface;
readMe: string;
packageName: string;
enableLoading: () => void;
}
export const DetailContext = React.createContext<DetailContextProps | null>(null);
export const DetailContext = React.createContext<Partial<DetailContextProps>>({});
export interface VersionPageConsumerProps {
packageMeta: any;
readMe: any;
packageName: any;
enableLoading: any;
packageMeta: PackageMetaInterface;
readMe: string;
packageName: string;
enableLoading: () => void;
}
export const DetailContextProvider: Provider<VersionPageConsumerProps | null> = DetailContext.Provider;
export const DetailContextConsumer: Consumer<VersionPageConsumerProps | null> = DetailContext.Consumer;
export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> = DetailContext.Provider;
export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> = DetailContext.Consumer;
class VersionPage extends Component<any, any> {
constructor(props: any) {
interface PropsInterface {
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);
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 packageName = getRouterPackageName(match);
@ -65,10 +78,10 @@ class VersionPage extends Component<any, any> {
}
/* 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;
if (packageName !== prevState.packageName) {
const { readMe, packageMeta } = await callDetailPage(packageName);
const { readMe, packageMeta } = (await callDetailPage(packageName)) as Partial<StateInterface>;
this.setState({
readMe,
packageMeta,
@ -112,7 +125,7 @@ class VersionPage extends Component<any, any> {
});
try {
const { readMe, packageMeta } = await callDetailPage(packageName);
const { readMe, packageMeta } = (await callDetailPage(packageName)) as Partial<StateInterface>;
this.setState({
readMe,
packageMeta,

View File

@ -2,7 +2,7 @@
import React, { Component, ReactElement } from 'react';
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 history from './history';
@ -12,7 +12,12 @@ const NotFound = asyncComponent(() => import('./components/NotFound'));
const VersionPackage = asyncComponent(() => import('./pages/version/Version'));
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> {
return (
<Router history={history}>
@ -34,8 +39,8 @@ class RouterApp extends Component<any, any> {
return (
<AppContextConsumer>
{function renderConsumerVersionPage({ logoUrl, scope, user }: any) {
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user.username} />;
{function renderConsumerVersionPage({ logoUrl, scope = '', user }: Partial<AppStateInterface>) {
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user && user.username} />;
}}
</AppContextConsumer>
);
@ -44,7 +49,7 @@ class RouterApp extends Component<any, any> {
public renderHomePage = (): ReactElement<HTMLDivElement> => {
return (
<AppContextConsumer>
{function renderConsumerVersionPage({ isUserLoggedIn, packages }: any) {
{function renderConsumerVersionPage({ isUserLoggedIn, packages }: Partial<AppStateInterface>) {
// @ts-ignore
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 (
<AppContextConsumer>
{function renderConsumerVersionPage({ isUserLoggedIn }: any) {
{function renderConsumerVersionPage({ isUserLoggedIn }: Partial<AppStateInterface>) {
return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />;
}}
</AppContextConsumer>

View File

@ -6,9 +6,9 @@ import '../../types';
* @param {object} response
* @returns {promise}
*/
function handleResponseType(response): Promise<any> {
function handleResponseType(response: Response): Promise<[boolean, Blob | string]> | Promise<void> {
if (response.headers) {
const contentType = response.headers.get('Content-Type');
const contentType = response.headers.get('Content-Type') as string;
if (contentType.includes('application/pdf')) {
return Promise.all([response.ok, response.blob()]);
}
@ -25,16 +25,16 @@ function handleResponseType(response): Promise<any> {
}
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) {
throw new Error('VERDACCIO_API_URL is not defined!');
}
const token = storage.getItem('token');
if (token) {
if (!options.headers) options.headers = {};
options.headers.authorization = `Bearer ${token}`;
const headers = new Headers(options && options.headers);
if (token && options && options.headers) {
headers.set('Authorization', `Bearer ${token}`);
options.headers = Object.assign(options.headers, headers);
}
if (!['http://', 'https://', '//'].some(prefix => url.startsWith(prefix))) {
@ -42,7 +42,7 @@ class API {
url = window.VERDACCIO_API_URL + url;
}
return new Promise<any>((resolve, reject) => {
return new Promise((resolve, reject) => {
fetch(url, {
method,
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 {
static Component = null;
state = { Component: AsyncComponent.Component };
public static Component = null;
public state = { Component: AsyncComponent.Component };
componentDidMount() {
public componentDidMount(): void {
const { Component } = this.state;
if (!Component) {
getComponent()
@ -19,7 +19,8 @@ export function asyncComponent(getComponent) {
});
}
}
render() {
public render(): JSX.Element | null {
const { Component } = this.state;
if (Component) {
// eslint-disable-next-line verdaccio/jsx-spread

View File

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

View File

@ -1,6 +1,6 @@
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();
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');
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/**
* CSS to represent truncated text with an ellipsis.
*/
export function ellipsis(width: string | number) {
export function ellipsis(width: string | number): {} {
return {
display: 'inline-block',
maxWidth: width,
@ -24,7 +24,7 @@ interface SpacingShortHand<type> {
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 valuesWithDefaults = [firstValue, secondValue, thirdValue, fourthValue];
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
declare module '*.svg' {
const content: any;
const content: string;
export default content;
}
declare module '*.png' {
const content: any;
export default content;
}
declare module '*.png';

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: {};
}