1
0
mirror of https://github.com/SomboChea/ui synced 2026-01-17 16:45:49 +07:00

feat: migrating flow to typescript (#47)

This PR convert the code base to Typescript, the changes are the following:

- migrate code base to Typescript (3.4.x)
- enable `eslint` and `@typescript-eslint/eslint-plugin` (warnings still need to be addressed in future pull request
- update relevant dependencies for this PR (linting, etc)
- enable `bundlezise` (it was disabled for some reason)

* refactor: refactoring to typescript

* refactor: migrating to typescript

* refactor: applied feedbacks

* fix: fixed conflicts

* refactored: changed registry

* refactor: updated registry & removed unnecessary lib

* fix: fixed registry ur

* fix: fixed page load

* refactor: refactored footer wip

* refactor: converting to ts..wip

* refactor: converting to ts. wip

* refactor: converting to ts. wip

* refactor: converting to ts

* refactor: converting to ts

* fix: fixed load errors

* refactor: converted files to ts

* refactor: removed flow from tests

* fix: removed transpiled files

* refactor: added ts-ignore

* fix: fixed errors

* fix: fixed types

* fix: fixing jest import -.-

* fix: fixing lint errors

* fix: fixing lint errors

* fix: fixed lint errors

* refactor: removed unnecessary tsconfig's config

* fix: fixing errors

* fix: fixed warning

* fix: fixed test

* refactor: wip

* refactor: wip

* refactor: wip

* fix: fixing tests: wip

* wip

* wip

* fix: fixed search test

* wip

* fix: fixing lint errors

* fix: re-added stylelint

* refactor: updated stylelint script

* fix: fixed: 'styles.js'  were found.

* fix: fixed Search tests

* chore: enable eslint

eslint needs expecitely to know which file has to lint, by default is JS, in this case we need also ts,tsx files eslint . --ext .js,.ts

* chore: vcode eslint settings

* chore: restore eslint previous conf

* chore: clean jest config

* chore: fix eslint warnings

* chore: eslint errors cleared

chore: clean warnings

chore: remove github actions test phases

chore: remove dupe rule

* chore: update handler name

* chore: restore logo from img to url css prop

- loading images with css is more performant than using img html tags, switching this might be a breaking change
- restore no-empty-source seems the linting do not accept false
- update snapshots
- remove @material-ui/styles

* chore: update stylelint linting

* chore: update stylelint linting

* chore: fix a mistake on move tabs to a function

* chore: eanble bundlezie

* chore: use default_executor in circleci

* chore: update readme
This commit is contained in:
Priscila Oliveira
2019-06-20 14:37:28 +02:00
committed by Juan Picado @jotadeveloper
parent 7d1764458b
commit 6b5d0b7e2e
358 changed files with 4730 additions and 58431 deletions

5
src/components/.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"rules": {
"no-invalid-this": 0
}
}

View File

@@ -0,0 +1,83 @@
import React, { Component, ReactElement } from 'react';
import BugReportIcon from '@material-ui/icons/BugReport';
import DownloadIcon from '@material-ui/icons/CloudDownload';
import HomeIcon from '@material-ui/icons/Home';
import List from '@material-ui/core/List';
import Tooltip from '@material-ui/core/Tooltip';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { Fab, ActionListItem } from './styles';
import { isURL } from '../../utils/url';
const ACTIONS = {
homepage: {
icon: <HomeIcon />,
title: 'Visit homepage',
},
issue: {
icon: <BugReportIcon />,
title: 'Open an issue',
},
tarball: {
icon: <DownloadIcon />,
title: 'Download tarball',
},
};
class ActionBar extends Component<any, any> {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
return this.renderActionBar(context as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private renderIconsWithLink(link: string, component: any): ReactElement<HTMLElement> {
return (
<a href={link} target={'_blank'}>
{component}
</a>
);
}
private renderActionBarListItems = packageMeta => {
// @ts-ignore
const { latest: { bugs: { url: issue } = {}, homepage, dist: { tarball } = {} } = {} } = packageMeta;
const actionsMap = {
homepage,
issue,
tarball,
};
const renderList = Object.keys(actionsMap).reduce((component, value, key) => {
const link = actionsMap[value];
if (link && isURL(link)) {
const fab = <Fab size={'small'}>{ACTIONS[value]['icon']}</Fab>;
component.push(
// @ts-ignore
<Tooltip key={key} title={ACTIONS[value]['title']}>
<>{this.renderIconsWithLink(link, fab)}</>
</Tooltip>
);
}
return component;
}, []);
return (
<>
<ActionListItem alignItems={'flex-start'}>{renderList}</ActionListItem>
</>
);
};
private renderActionBar = ({ packageMeta = {} }) => {
return <List>{this.renderActionBarListItems(packageMeta)}</List>;
};
}
export default ActionBar;

View File

@@ -0,0 +1 @@
export { default } from './ActionBar';

View File

@@ -0,0 +1,21 @@
import styled from 'react-emotion';
import { default as MuiFab } from '@material-ui/core/Fab';
import ListItem from '@material-ui/core/ListItem';
import colors from '../../utils/styles/colors';
export const ActionListItem = styled(ListItem)`
&& {
padding-top: 0;
padding-left: 0;
padding-right: 0;
}
`;
export const Fab = styled(MuiFab)`
&& {
background-color: ${colors.primary};
color: ${colors.white};
margin-right: 10px;
}
`;

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import Author from './Author';
describe('<Author /> component', () => {
test('should render the component in default state', () => {
const wrapper = shallow(<Author />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,53 @@
import React, { Component, ReactNode } from 'react';
import Avatar from '@material-ui/core/Avatar';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import { DetailContextConsumer } from '../../pages/version/Version';
import { Heading, AuthorListItem } from './styles';
import { isEmail } from '../../utils/url';
class Authors extends Component<any, any> {
render() {
return (
<DetailContextConsumer>
{(context: any) => {
return context && context.packageMeta && this.renderAuthor(context.packageMeta);
}}
</DetailContextConsumer>
);
}
renderLinkForMail(email: string, avatarComponent: ReactNode, packageName: string, version: string) {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
}
renderAuthor = packageMeta => {
const { author, name: packageName, version } = packageMeta.latest;
if (!author) {
return null;
}
const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
return (
<List subheader={<Heading variant={'subheading'}>{'Author'}</Heading>}>
<AuthorListItem>
{this.renderLinkForMail(author.email, avatarComponent, packageName, version)}
<ListItemText primary={author.name} />
</AuthorListItem>
</List>
);
};
}
export default Authors;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Author /> component should render the component in default state 1`] = `""`;

View File

@@ -0,0 +1 @@
export { default } from './Author';

View File

@@ -0,0 +1,16 @@
import styled from 'react-emotion';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
export const Heading = styled(Typography)`
&& {
font-weight: 700;
text-transform: capitalize;
}
`;
export const AuthorListItem = styled(ListItem)`
&& {
padding-left: 0;
padding-right: 0;
}
`;

View File

@@ -0,0 +1,140 @@
import React, { KeyboardEvent } from 'react';
import Autosuggest from 'react-autosuggest';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import MenuItem from '@material-ui/core/MenuItem';
import { fontWeight } from '../../utils/styles/sizes';
import { Wrapper, InputField, SuggestionContainer } from './styles';
export interface Props {
suggestions: any[];
suggestionsLoading?: boolean;
suggestionsLoaded?: boolean;
suggestionsError?: boolean;
apiLoading?: boolean;
color?: string;
value?: string;
placeholder?: string;
startAdornment?: any;
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;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: KeyboardEvent<HTMLInputElement>) => void;
}
const renderInputComponent = inputProps => {
const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps;
return (
<InputField
InputProps={{
inputRef: node => {
ref(node);
},
startAdornment,
disableUnderline,
onKeyDown,
}}
fullWidth={true}
{...others}
/>
);
};
const getSuggestionValue = (suggestion): string => suggestion.name;
const renderSuggestion = (suggestion, { query, isHighlighted }) => {
const matches = match(suggestion.name, query);
const parts = parse(suggestion.name, matches);
return (
<MenuItem component="div" selected={isHighlighted}>
<div>
{parts.map((part, index) => {
return part.highlight ? (
<a href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.semiBold }}>
{part.text}
</a>
) : (
<a href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.light }}>
{part.text}
</a>
);
})}
</div>
</MenuItem>
);
};
const renderMessage = message => {
return (
<MenuItem component="div" selected={false}>
<div>{message}</div>
</MenuItem>
);
};
const SUGGESTIONS_RESPONSE = {
LOADING: 'Loading...',
FAILURE: 'Something went wrong.',
NO_RESULT: 'No results found.',
};
const AutoComplete = ({
suggestions,
startAdornment,
onChange,
onSuggestionsFetch,
onCleanSuggestions,
value = '',
placeholder = '',
disableUnderline = false,
color,
onClick,
onKeyDown,
onBlur,
suggestionsLoading = false,
suggestionsLoaded = false,
suggestionsError = false,
}: Props) => {
const autosuggestProps = {
renderInputComponent,
suggestions,
getSuggestionValue,
renderSuggestion,
onSuggestionsFetchRequested: onSuggestionsFetch,
onSuggestionsClearRequested: onCleanSuggestions,
};
const inputProps = {
value,
onChange,
placeholder,
startAdornment,
disableUnderline,
color,
onKeyDown,
onBlur,
};
// this format avoid arrow function eslint rule
function renderSuggestionsContainer({ containerProps, children, query }) {
return (
<SuggestionContainer {...containerProps} square={true}>
{suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)}
{suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)}
{suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)}
{children}
</SuggestionContainer>
);
}
return (
<Wrapper>
<Autosuggest {...autosuggestProps} inputProps={inputProps} onSuggestionSelected={onClick} renderSuggestionsContainer={renderSuggestionsContainer} />
</Wrapper>
);
};
export default AutoComplete;

View File

@@ -0,0 +1 @@
export { default } from './AutoComplete';

View File

@@ -0,0 +1,59 @@
import React from 'react';
import styled, { css } from 'react-emotion';
import Paper from '@material-ui/core/Paper';
import TextField from '../TextField';
export interface InputFieldProps {
color: string;
}
export const Wrapper = styled('div')`
&& {
width: 100%;
height: 32px;
position: relative;
z-index: 1;
}
`;
export const InputField: React.FC<InputFieldProps> = ({ color, ...others }) => (
<TextField
{...others}
classes={{
// @ts-ignore
input: css`
&& {
${color &&
css`
color: ${color};
`};
}
`,
root: css`
&& {
&:before {
content: '';
border: none;
}
&:after {
${color &&
css`
border-color: ${color};
`};
}
&:hover:before {
content: none;
}
}
`,
}}
/>
);
export const SuggestionContainer = styled(Paper)`
&& {
max-height: 500px;
overflow-y: auto;
}
`;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { shallow } from 'enzyme';
import CopyToClipBoard from './CopyToClipBoard';
import { CopyIcon } from './styles';
describe('<CopyToClipBoard /> component', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<CopyToClipBoard text={'copy text'} />);
});
test('render the component', () => {
expect(wrapper.html()).toMatchSnapshot();
});
test('should call the DOM APIs for copy to clipboard utility', () => {
const event = {
preventDefault: jest.fn(),
};
// @ts-ignore: Property 'getSelection' does not exist on type 'Global'.
global.getSelection = jest.fn(() => ({
removeAllRanges: () => {},
addRange: () => {},
}));
// @ts-ignore: Property 'document/getSelection' does not exist on type 'Global'.
const { document, getSelection } = global;
wrapper.find(CopyIcon).simulate('click', event);
expect(event.preventDefault).toHaveBeenCalled();
expect(document.createRange).toHaveBeenCalled();
expect(getSelection).toHaveBeenCalled();
expect(document.execCommand).toHaveBeenCalledWith('copy');
});
});

View File

@@ -0,0 +1,40 @@
import Tooltip from '@material-ui/core/Tooltip';
import FileCopy from '@material-ui/icons/FileCopy';
import React from 'react';
import { copyToClipBoardUtility } from '../../utils/cli-utils';
import { TEXT } from '../../utils/constants';
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
interface Props {
text: string;
children?: React.ReactNode;
}
const renderText: React.FC<any> = (text: string, children: React.ReactNode): React.ReactElement<HTMLElement> => {
if (children) {
return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
}
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
};
const renderToolTipFileCopy = (text: string): React.ReactElement<HTMLElement> => (
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>
);
const CopyToClipBoard: React.FC<Props> = ({ text, children }) => {
return (
<ClipBoardCopy>
{renderText(text, children)}
{renderToolTipFileCopy(text)}
</ClipBoardCopy>
);
};
export default CopyToClipBoard;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CopyToClipBoard /> component render the component 1`] = `"<div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root-15 MuiIconButton-root-9 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-14\\"><svg class=\\"MuiSvgIcon-root-18\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './CopyToClipBoard';

View File

@@ -0,0 +1,26 @@
import IconButton from '@material-ui/core/IconButton';
import styled from 'react-emotion';
export const ClipBoardCopy = styled('div')`
&& {
display: flex;
align-items: center;
justify-content: space-between;
}
`;
export const ClipBoardCopyText = styled('span')`
&& {
display: inline-block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
height: 21px;
}
`;
export const CopyIcon = styled(IconButton)`
&& {
margin: 0 0 0 10px;
}
`;

View File

@@ -0,0 +1,108 @@
import React, { Component, Fragment, ReactElement } from 'react';
import { withRouter } from 'react-router-dom';
import CardContent from '@material-ui/core/CardContent';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { CardWrap, Heading, Tags, Tag } from './styles';
import NoItems from '../NoItems';
class DepDetail extends Component<any, any> {
constructor(props: any) {
super(props);
const { name, version } = this.props;
this.state = {
name,
version,
};
}
public render(): ReactElement<HTMLElement> {
const { name, version } = this.state;
const tagText = `${name}@${version}`;
return <Tag className={'dep-tag'} clickable={true} component={'div'} label={tagText} onClick={this.handleOnClick} />;
}
private handleOnClick = () => {
const { name } = this.state;
const { onLoading, history } = this.props;
onLoading();
history.push(`/-/web/detail/${name}`);
};
}
const WrapperDependencyDetail = withRouter(DepDetail);
class DependencyBlock extends Component<any, any> {
public render(): ReactElement<HTMLElement> {
const { dependencies, title } = this.props;
const deps = Object.entries(dependencies);
return (
<DetailContextConsumer>
{({ enableLoading }: any) => {
return (
<CardWrap>
<CardContent>
<Heading variant="subheading">{`${title} (${deps.length})`}</Heading>
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
</CardContent>
</CardWrap>
);
}}
</DetailContextConsumer>
);
}
private renderTags = (deps: any, enableLoading: any) =>
deps.map(dep => {
const [name, version] = dep;
return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />;
});
}
class Dependencies extends Component<any, any> {
public state = {
tabPosition: 0,
};
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{packageMeta => {
return this.renderDependencies(packageMeta as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private checkDependencyLength(dependency: Record<string, any> = {}): boolean {
return Object.keys(dependency).length > 0;
}
private renderDependencies({ packageMeta }): ReactElement<HTMLElement> {
const { latest } = packageMeta;
const { dependencies, devDependencies, peerDependencies, name } = latest;
const dependencyMap = { dependencies, devDependencies, peerDependencies };
const dependencyList = Object.keys(dependencyMap).reduce((result, value, key) => {
const selectedDepndency = dependencyMap[value];
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
// @ts-ignore
result.push(<DependencyBlock className="dependency-block" dependencies={selectedDepndency} key={key} title={value} />);
}
return result;
}, []);
if (dependencyList.length) {
return <Fragment>{dependencyList}</Fragment>;
}
return <NoItems className="no-dependencies" text={`${name} has no dependencies.`} />;
}
}
export default Dependencies;

View File

@@ -0,0 +1 @@
export { default } from './Dependencies';

View File

@@ -0,0 +1,32 @@
import styled from 'react-emotion';
import Card from '@material-ui/core/Card';
import Typography from '@material-ui/core/Typography';
import Chip from '@material-ui/core/Chip';
export const CardWrap = styled(Card)`
&& {
margin: 0 0 16px;
}
`;
export const Heading = styled(Typography)`
&& {
font-weight: 700;
text-transform: capitalize;
}
`;
export const Tags = styled('div')`
&& {
display: flex;
justify-content: start;
flex-wrap: wrap;
margin: 0 -5px;
}
`;
export const Tag = styled(Chip)`
&& {
margin: 5px;
}
`;

View File

@@ -0,0 +1,7 @@
import { ReactNode } from 'react';
export interface Props {
children: ReactNode;
open: boolean;
onClose: () => void;
}

View File

@@ -0,0 +1,72 @@
import React, { Component, ReactElement, Fragment } from 'react';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import Readme from '../Readme';
import Versions from '../Versions';
import { preventXSS } from '../../utils/sec-utils';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import { Content } from './styles';
import Dependencies from '../Dependencies';
import UpLinks from '../UpLinks';
interface DetailContainerState {
tabPosition: number;
}
class DetailContainer extends Component<any, DetailContainerState> {
public state = {
tabPosition: 0,
};
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
return this.renderTabs(context as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private handleChange = (event: any, tabPosition: number) => {
event.preventDefault();
this.setState({ tabPosition });
};
private renderListTabs(tabPosition: number): React.ReactElement<HTMLElement> {
return (
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
<Tab id={'readme-tab'} label={'Readme'} />
<Tab id={'dependencies-tab'} label={'Dependencies'} />
<Tab id={'versions-tab'} label={'Versions'} />
<Tab id={'uplinks-tab'} label={'Uplinks'} />
</Tabs>
);
}
private renderTabs = ({ readMe }) => {
const { tabPosition } = this.state;
return (
<Fragment>
<Content>
{this.renderListTabs(tabPosition)}
<br />
{tabPosition === 0 && this.renderReadme(readMe)}
{tabPosition === 1 && <Dependencies />}
{tabPosition === 2 && <Versions />}
{tabPosition === 3 && <UpLinks />}
</Content>
</Fragment>
);
};
private renderReadme = (readMe: string): ReactElement<HTMLElement> => {
const encodedReadme = preventXSS(readMe);
return <Readme description={encodedReadme} />;
};
}
export default DetailContainer;

View File

@@ -0,0 +1 @@
export { default } from './DetailContainer';

View File

@@ -0,0 +1,7 @@
import styled from 'react-emotion';
export const Content = styled('div')`
&& {
padding: 15px;
}
`;

View File

@@ -0,0 +1,7 @@
import { ReactNode } from 'react';
export interface Props {
children: ReactNode;
open: boolean;
onClose: () => void;
}

View File

@@ -0,0 +1,87 @@
import React, { Component, ReactElement } from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import List from '@material-ui/core/List';
import ActionBar from '../ActionBar/ActionBar';
import Author from '../Author';
import Developers from '../Developers';
import Dist from '../Dist/Dist';
import Engine from '../Engines/Engines';
import Install from '../Install';
import Repository from '../Repository/Repository';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { TitleListItem, TitleListItemText } from './styles';
class DetailSidebar extends Component {
public render(): ReactElement<HTMLElement> {
return <DetailContextConsumer>{context => this.renderSideBar(context as VersionPageConsumerProps)}</DetailContextConsumer>;
}
private renderSideBar = ({ packageName, packageMeta }): ReactElement<HTMLElement> => {
return (
<div className={'sidebar-info'}>
<Card>
<CardContent>
{this.renderTitle(packageName, packageMeta)}
{this.renderActionBar()}
{this.renderCopyCLI()}
{this.renderRepository()}
{this.renderEngine()}
{this.renderDist()}
{this.renderAuthor()}
{this.renderMaintainers()}
{this.renderContributors()}
</CardContent>
</Card>
</div>
);
};
private renderTitle = (packageName, packageMeta) => {
return (
<List className="detail-info">
<TitleListItem alignItems="flex-start">
<TitleListItemText primary={<b>{packageName}</b>} secondary={packageMeta.latest.description} />
</TitleListItem>
</List>
);
};
private renderCopyCLI = () => {
return <Install />;
};
private renderMaintainers = () => {
return <Developers type="maintainers" />;
};
private renderContributors = () => {
return <Developers type="contributors" />;
};
private renderRepository = () => {
return <Repository />;
};
private renderAuthor = () => {
return <Author />;
};
private renderEngine = () => {
return <Engine />;
};
private renderDist = () => {
return <Dist />;
};
private renderActionBar = () => {
return <ActionBar />;
};
}
export default DetailSidebar;

View File

@@ -0,0 +1 @@
export { default } from './DetailSidebar';

View File

@@ -0,0 +1,30 @@
import styled from 'react-emotion';
import Avatar from '@material-ui/core/Avatar';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import colors from '../../utils/styles/colors';
export const TitleListItem = styled(ListItem)`
&& {
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
}
`;
export const TitleListItemText = styled(ListItemText)`
&& {
padding-left: 0;
padding-right: 0;
padding-top: 8px;
}
`;
export const TitleAvatar = styled(Avatar)`
&& {
color: ${colors.greySuperLight};
background-color: ${colors.primary};
text-transform: capitalize;
}
`;

View File

@@ -0,0 +1,76 @@
import React, { Component } from 'react';
import Avatar from '@material-ui/core/Avatar';
import Add from '@material-ui/icons/Add';
import Tooltip from '@material-ui/core/Tooltip';
import { DetailContextConsumer } from '../../pages/version/Version';
import { Details, Heading, Content, Fab } from './styles';
import { isEmail } from '../../utils/url';
interface Props {
type: 'contributors' | 'maintainers';
}
class Developers extends Component<Props, any> {
state = {
visibleDevs: 6,
};
render() {
return (
<DetailContextConsumer>
{({ packageMeta }: any) => {
const { type } = this.props;
const developerType = packageMeta.latest[type];
if (!developerType || developerType.length === 0) return null;
return this.renderDevelopers(developerType, packageMeta);
}}
</DetailContextConsumer>
);
}
handleLoadMore = () => {
this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 }));
};
renderDevelopers = (developers, packageMeta) => {
const { type } = this.props;
const { visibleDevs } = this.state;
return (
<>
<Heading variant={'subheading'}>{type}</Heading>
<Content>
{developers.slice(0, visibleDevs).map(developer => (
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details>
))}
{visibleDevs < developers.length && (
<Fab onClick={this.handleLoadMore} size="small">
<Add />
</Fab>
)}
</Content>
</>
);
};
renderLinkForMail(email, avatarComponent, packageName, version) {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
}
renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
const { name: packageName, version } = packageMeta.latest;
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
return <Tooltip title={name}>{this.renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
};
}
export default Developers;

View File

@@ -0,0 +1 @@
export { default } from './Developers';

View File

@@ -0,0 +1,34 @@
import styled from 'react-emotion';
import Typography from '@material-ui/core/Typography';
import { default as MuiFab } from '@material-ui/core/Fab';
import colors from '../../utils/styles/colors';
export const Details = styled('span')`
display: flex;
flex-direction: column;
align-items: center;
`;
export const Content = styled('div')`
margin: 10px 0 10px 0;
display: flex;
flex-wrap: wrap;
> * {
margin: 5px;
}
`;
export const Heading = styled(Typography)`
&& {
font-weight: 700;
margin-bottom: 10px;
text-transform: capitalize;
}
`;
export const Fab = styled(MuiFab)`
&& {
background-color: ${colors.primary};
color: ${colors.white};
}
`;

View File

@@ -0,0 +1,57 @@
import React, { Component } from 'react';
import List from '@material-ui/core/List';
import { DetailContextConsumer } from '../../pages/version/Version';
import { Heading, DistListItem, DistChips } from './styles';
import fileSizeSI from '../../utils/file-size';
class Dist extends Component<any, any> {
render() {
return (
<DetailContextConsumer>
{(context: any) => {
return this.renderDist(context);
}}
</DetailContextConsumer>
);
}
renderChips(dist: any, license: string) {
const distDict = {
'file-count': dist.fileCount,
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
license,
};
const chipsList = Object.keys(distDict).reduce((componentList, title, key) => {
// @ts-ignore
const value = distDict[title];
if (value) {
const label = (
<span>
{/* eslint-disable-next-line */}
<b>{title.split('-').join(' ')}</b>:{value}
</span>
);
// @ts-ignore is not assignable to parameter of type 'never'
componentList.push(<DistChips key={key} label={label} />);
}
return componentList;
}, []);
return chipsList;
}
renderDist = ({ packageMeta }: any) => {
const { dist = {}, license } = packageMeta.latest;
return (
<List subheader={<Heading variant="subheading">{'Latest Distribution'}</Heading>}>
<DistListItem>{this.renderChips(dist, license)}</DistListItem>
</List>
);
};
}
export default Dist;

View File

@@ -0,0 +1 @@
export { default } from './Dist';

View File

@@ -0,0 +1,35 @@
import styled from 'react-emotion';
import { default as MuiFab } from '@material-ui/core/Fab';
import Chip from '@material-ui/core/Chip';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
import colors from '../../utils/styles/colors';
export const Heading = styled(Typography)`
&& {
font-weight: 700;
text-transform: capitalize;
}
`;
export const DistListItem = styled(ListItem)`
&& {
padding-left: 0;
padding-right: 0;
}
`;
export const DistChips = styled(Chip)`
&& {
margin-right: 5px;
text-transform: capitalize;
}
`;
export const DownloadButton = styled(MuiFab)`
&& {
background-color: ${colors.primary};
color: ${colors.white};
}
`;

View File

@@ -0,0 +1,73 @@
import React, { Component, ReactElement } from 'react';
import Avatar from '@material-ui/core/Avatar';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
import { Heading, EngineListItem } from './styles';
// @ts-ignore
import node from './img/node.png';
import npm from '../Install/img/npm.svg';
const ICONS = {
'node-JS': <Avatar src={node} />,
'NPM-version': <Avatar src={npm} />,
};
class Engine extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
return this.renderEngine(context as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private renderEngine = ({ packageMeta }): ReactElement<HTMLElement> | null => {
const { engines } = packageMeta.latest;
if (!engines) {
return null;
}
const engineDict = {
'node-JS': engines.node,
'NPM-version': engines.npm,
};
const accumulator: React.ReactNode[] = [];
const items = Object.keys(engineDict).reduce((markup, text, key) => {
const heading = engineDict[text];
if (heading) {
markup.push(
<Grid item={true} key={key} xs={6}>
{this.renderListItems(heading, text)}
</Grid>
);
}
return markup;
}, accumulator);
if (items.length < 1) {
return null;
}
return <Grid container={true}>{items}</Grid>;
};
private renderListItems = (heading, text) => {
return (
<List subheader={<Heading variant={'subheading'}>{text.split('-').join(' ')}</Heading>}>
<EngineListItem>
{ICONS[text]}
<ListItemText primary={heading} />
</EngineListItem>
</List>
);
};
}
export default Engine;

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1 @@
export { default } from './Engines';

View File

@@ -0,0 +1,16 @@
import styled from 'react-emotion';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
export const Heading = styled(Typography)`
&& {
font-weight: 700;
text-transform: capitalize;
}
`;
export const EngineListItem = styled(ListItem)`
&& {
padding-left: 0;
}
`;

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { mount } from 'enzyme';
import Footer from './Footer';
jest.mock('../../../package.json', () => ({
version: '4.0.0-alpha.3',
}));
describe('<Footer /> component', () => {
let wrapper;
beforeEach(() => {
// @ts-ignore : Property 'VERDACCIO_VERSION' does not exist on type 'Window'
window.VERDACCIO_VERSION = 'v.1.0.0';
wrapper = mount(<Footer />);
// @ts-ignore : Property 'VERDACCIO_VERSION' does not exist on type 'Window'
delete window.VERDACCIO_VERSION;
});
test('should load the initial state of Footer component', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
import { goToVerdaccioWebsite } from '../../utils/windows';
const renderTooltip = () => (
<ToolTip>
<Earth name="earth" size="md" />
<Flags>
<Flag name="spain" size="md" />
<Flag name="nicaragua" size="md" />
<Flag name="india" size="md" />
<Flag name="brazil" size="md" />
<Flag name="china" size="md" />
<Flag name="austria" size="md" />
</Flags>
</ToolTip>
);
const POWERED_LABEL = 'Powered by';
const MADEWITH_LABEL = ' Made with';
const ON_LABEL = 'on';
const HEARTH_EMOJI = '♥';
// @ts-ignore
const renderRight = (version = window.VERDACCIO_VERSION) => {
return (
<Right>
{POWERED_LABEL}
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
{`/ ${version}`}
</Right>
);
};
const renderLeft = () => (
<Left>
{MADEWITH_LABEL}
<Love>{HEARTH_EMOJI}</Love>
{ON_LABEL}
{renderTooltip()}
</Left>
);
const Footer: React.FC = () => (
<Wrapper>
<Inner>
{renderLeft()}
{renderRight()}
</Inner>
</Wrapper>
);
export default Footer;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-1kgp95j ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Footer';

View File

@@ -0,0 +1,112 @@
import styled, { css } from 'react-emotion';
import mq from '../../utils/styles/media';
import Icon from '../Icon/Icon';
import colors from '../../utils/styles/colors';
export const Wrapper = styled('div')`
&& {
background: ${colors.snow};
border-top: 1px solid ${colors.greyGainsboro};
color: ${colors.nobel01};
font-size: 14px;
padding: 20px;
}
`;
export const Inner = styled('div')`
&& {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
${() => {
// @ts-ignore
return mq.medium(css`
min-width: 400px;
max-width: 800px;
margin: auto;
justify-content: space-between;
`);
}};
${() => {
// @ts-ignore
return mq.large(css`
max-width: 1240px;
`);
}};
}
`;
export const Left = styled('div')`
&& {
align-items: center;
display: none;
${() => {
// @ts-ignore
return mq.medium(css`
display: flex;
`);
}};
}
`;
export const Right = styled(Left)`
&& {
display: flex;
}
`;
export const ToolTip = styled('span')`
&& {
position: relative;
height: 18px;
}
`;
export const Earth = styled(Icon)`
&& {
padding: 0 10px;
}
`;
export const Flags = styled('span')`
&& {
position: absolute;
background: ${colors.greyAthens};
padding: 1px 4px;
border-radius: 3px;
height: 20px;
display: inline-flex;
align-items: center;
visibility: hidden;
top: -2px;
:before {
content: '';
position: absolute;
top: 29%;
left: -4px;
margin-left: -5px;
border: 5px solid;
border-color: ${colors.greyAthens} transparent transparent transparent;
transform: rotate(90deg);
}
${ToolTip}:hover & {
visibility: visible;
}
}
`;
export const Love = styled('span')`
&& {
color: ${colors.love};
padding: 0 5px;
}
`;
export const Flag = styled(Icon)`
&& {
padding: 0 5px;
}
`;
export const Logo = Flag;

View File

@@ -0,0 +1,124 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { shallow } from 'enzyme';
import Header from './Header';
describe('<Header /> component with logged in state', () => {
let wrapper;
let routerWrapper;
let instance;
let props;
beforeEach(() => {
props = {
username: 'test user',
handleLogout: jest.fn(),
logo: '',
onToggleLoginModal: jest.fn(),
scope: 'test scope',
withoutSearch: true,
};
routerWrapper = shallow(
<Router>
<Header
logo={props.logo}
onLogout={props.handleLogout}
onToggleLoginModal={props.onToggleLoginModal}
scope={props.scope}
username={props.username}
withoutSearch={props.withoutSearch}
/>
</Router>
);
wrapper = routerWrapper.find(Header).dive();
instance = wrapper.instance();
});
test('should load the component in logged in state', () => {
const state = {
openInfoDialog: false,
packages: undefined,
registryUrl: 'http://localhost',
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(routerWrapper.html()).toMatchSnapshot();
});
test('handleLoggedInMenu: set anchorEl to html element value in state', () => {
// creates a sample menu
const div = document.createElement('div');
const text = document.createTextNode('sample menu');
div.appendChild(text);
const event = {
currentTarget: div,
};
instance.handleLoggedInMenu(event);
expect(wrapper.state('anchorEl')).toEqual(div);
});
});
describe('<Header /> component with logged out state', () => {
let wrapper;
let routerWrapper;
let instance;
let props;
beforeEach(() => {
props = {
handleLogout: jest.fn(),
onToggleLoginModal: jest.fn(),
scope: 'test scope',
logo: '',
withoutSearch: true,
};
routerWrapper = shallow(
<Router>
<Header
logo={props.logo}
onLogout={props.handleLogout}
onToggleLoginModal={props.onToggleLoginModal}
scope={props.scope}
withoutSearch={props.withoutSearch}
/>
</Router>
);
wrapper = routerWrapper.find(Header).dive();
instance = wrapper.instance();
});
test('should load the component in logged out state', () => {
const state = {
openInfoDialog: false,
packages: undefined,
registryUrl: 'http://localhost',
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(routerWrapper.html()).toMatchSnapshot();
});
test('handleLoggedInMenuClose: set anchorEl value to null in state', () => {
instance.handleLoggedInMenuClose();
expect(wrapper.state('anchorEl')).toBeNull();
});
test('handleOpenRegistryInfoDialog: set openInfoDialog to be truthy in state', () => {
instance.handleOpenRegistryInfoDialog();
expect(wrapper.state('openInfoDialog')).toBeTruthy();
});
test('handleCloseRegistryInfoDialog: set openInfoDialog to be falsy in state', () => {
instance.handleCloseRegistryInfoDialog();
expect(wrapper.state('openInfoDialog')).toBeFalsy();
});
test('handleToggleLogin: close/open popover menu', () => {
instance.handleToggleLogin();
expect(wrapper.state('anchorEl')).toBeNull();
expect(props.onToggleLoginModal).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,272 @@
import React, { SyntheticEvent, Component, Fragment, ReactElement } from 'react';
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import Info from '@material-ui/icons/Info';
import Help from '@material-ui/icons/Help';
import Tooltip from '@material-ui/core/Tooltip';
import AccountCircle from '@material-ui/icons/AccountCircle';
import { default as IconSearch } from '@material-ui/icons/Search';
import { getRegistryURL } from '../../utils/url';
import ExternalLink from '../Link';
import Logo from '../Logo';
import RegistryInfoDialog from '../RegistryInfoDialog/RegistryInfoDialog';
import Label from '../Label/Label';
import Search from '../Search/Search';
import RegistryInfoContent from '../RegistryInfoContent/RegistryInfoContent';
import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles';
interface Props {
logo: string;
username?: string;
onLogout: () => void;
onToggleLoginModal: () => void;
scope: string;
withoutSearch?: boolean;
}
interface State {
anchorEl?: any;
openInfoDialog: boolean;
registryUrl: string;
showMobileNavBar: boolean;
}
type ToolTipType = 'search' | 'help' | 'info';
class Header extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
openInfoDialog: false,
registryUrl: getRegistryURL(),
showMobileNavBar: false,
};
}
public render(): ReactElement<HTMLElement> {
const { showMobileNavBar } = this.state;
const { withoutSearch = false } = this.props;
return (
<div>
<NavBar position="static">
<InnerNavBar>
{this.renderLeftSide()}
{this.renderRightSide()}
</InnerNavBar>
{this.renderInfoDialog()}
</NavBar>
{showMobileNavBar && !withoutSearch && (
<MobileNavBar>
<InnerMobileNavBar>
<Search />
</InnerMobileNavBar>
<Button color="inherit" onClick={this.handleDismissMNav}>
{'Cancel'}
</Button>
</MobileNavBar>
)}
</div>
);
}
/**
* opens popover menu for logged in user.
*/
public handleLoggedInMenu = (event: SyntheticEvent<HTMLElement>) => {
this.setState({
anchorEl: event.currentTarget,
});
};
/**
* closes popover menu for logged in user
*/
public handleLoggedInMenuClose = () => {
this.setState({
anchorEl: null,
});
};
/**
* opens registry information dialog.
*/
public handleOpenRegistryInfoDialog = () => {
this.setState({
openInfoDialog: true,
});
};
/**
* closes registry information dialog.
*/
public handleCloseRegistryInfoDialog = () => {
this.setState({
openInfoDialog: false,
});
};
/**
* close/open popover menu for logged in users.
*/
public handleToggleLogin = () => {
const { onToggleLoginModal } = this.props;
this.setState(
{
anchorEl: null,
},
onToggleLoginModal
);
};
public handleToggleMNav = () => {
const { showMobileNavBar } = this.state;
this.setState({
showMobileNavBar: !showMobileNavBar,
});
};
public handleDismissMNav = () => {
this.setState({
showMobileNavBar: false,
});
};
public renderLeftSide = () => {
const { withoutSearch = false } = this.props;
return (
<LeftSide>
<Link style={{ marginRight: '1em' }} to={'/'}>
{this.renderLogo()}
</Link>
{!withoutSearch && (
<SearchWrapper>
<Search />
</SearchWrapper>
)}
</LeftSide>
);
};
public renderLogo = () => {
const { logo } = this.props;
if (logo) {
return <img alt="logo" height="40px" src={logo} />;
} else {
return <Logo />;
}
};
public renderToolTipIcon = (title: string, type: ToolTipType) => {
let content;
switch (type) {
case 'help':
content = (
// @ts-ignore
<IconButton blank={true} color={'inherit'} component={ExternalLink} to={'https://verdaccio.org/docs/en/installation'}>
<Help />
</IconButton>
);
break;
case 'info':
content = (
<IconButton color="inherit" id="header--button-registryInfo" onClick={this.handleOpenRegistryInfoDialog}>
<Info />
</IconButton>
);
break;
case 'search':
content = (
<IconSearchButton color="inherit" onClick={this.handleToggleMNav}>
<IconSearch />
</IconSearchButton>
);
break;
}
return (
<Tooltip disableFocusListener={true} title={title}>
{content}
</Tooltip>
);
};
public renderRightSide = () => {
const { username = '', withoutSearch = false } = this.props;
return (
<RightSide>
{!withoutSearch && this.renderToolTipIcon('Search packages', 'search')}
{this.renderToolTipIcon('Documentation', 'help')}
{this.renderToolTipIcon('Registry Information', 'info')}
{username ? (
this.renderMenu()
) : (
<Button color="inherit" id="header--button-login" onClick={this.handleToggleLogin}>
{'Login'}
</Button>
)}
</RightSide>
);
};
private renderGreetings = () => {
const { username = '' } = this.props;
return (
<Fragment>
<Greetings>{'Hi,'}</Greetings>
<Label capitalize={true} text={username} weight="bold" />
</Fragment>
);
};
/**
* render popover menu
*/
private renderMenu = () => {
const { onLogout } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<>
<IconButton color="inherit" id="header--button-account" onClick={this.handleLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id="sidebar-menu"
onClose={this.handleLoggedInMenuClose}
open={open}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
<MenuItem disabled={true}>{this.renderGreetings()}</MenuItem>
<MenuItem id="header--button-logout" onClick={onLogout}>
{'Logout'}
</MenuItem>
</Menu>
</>
);
};
private renderInfoDialog = () => {
const { scope } = this.props;
const { openInfoDialog, registryUrl } = this.state;
return (
<RegistryInfoDialog onClose={this.handleCloseRegistryInfoDialog} open={openInfoDialog}>
<RegistryInfoContent registryUrl={registryUrl} scope={scope} />
</RegistryInfoDialog>
);
};
}
export default Header;

View File

@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> component with logged in state should load the component in logged in state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-rfunvc e1jf5lit8\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1jf5lit0\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1jf5lit3\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1jf5lit2\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-account\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span></button></div></div></header></div>"`;
exports[`<Header /> component with logged out state should load the component in logged out state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-rfunvc e1jf5lit8\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1jf5lit0\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1jf5lit3\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1jf5lit2\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiButton-root-85 MuiButton-text-87 MuiButton-flat-90 MuiButton-colorInherit-106\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-86\\">Login</span></button></div></div></header></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Header';

View File

@@ -0,0 +1,110 @@
import styled, { css } from 'react-emotion';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import colors from '../../utils/styles/colors';
import mq from '../../utils/styles/media';
export const InnerNavBar = styled(Toolbar)`
&& {
justify-content: space-between;
align-items: center;
padding: 0 15px;
}
`;
export const Greetings = styled('span')`
&& {
margin: 0 5px 0 0;
}
`;
export const RightSide = styled(Toolbar)`
&& {
display: flex;
padding: 0;
}
`;
export const LeftSide = styled(RightSide)`
&& {
flex: 1;
}
`;
export const MobileNavBar = styled('div')`
&& {
align-items: center;
display: flex;
border-bottom: 1px solid ${colors.greyLight};
padding: 8px;
position: relative;
}
`;
export const InnerMobileNavBar = styled('div')`
&& {
border-radius: 4px;
background-color: ${colors.greyLight};
color: ${colors.white};
width: 100%;
padding: 0px 5px;
margin: 0 10px 0 0;
}
`;
export const IconSearchButton = styled(IconButton)`
&& {
display: block;
}
`;
export const SearchWrapper = styled('div')`
&& {
display: none;
max-width: 393px;
width: 100%;
}
`;
export const NavBar = styled(AppBar)`
&& {
background-color: ${colors.primary};
min-height: 60px;
display: flex;
justify-content: center;
${() => {
// @ts-ignore
return mq.medium(css`
${SearchWrapper} {
display: flex;
}
${IconSearchButton} {
display: none;
}
${MobileNavBar} {
display: none;
}
`);
}};
${() => {
// @ts-ignore
return mq.large(css`
${InnerNavBar} {
padding: 0 20px;
}
`);
}};
${() => {
// @ts-ignore
return mq.xlarge(css`
${InnerNavBar} {
max-width: 1240px;
width: 100%;
margin: 0 auto;
}
`);
}};
}
`;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import Help from './Help';
describe('<Help /> component', () => {
test('should render the component in default state', () => {
const wrapper = shallow(<Help />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,46 @@
import Button from '@material-ui/core/Button';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import React, { Fragment } from 'react';
import { getRegistryURL } from '../../utils/url';
import CopyToClipBoard from '../CopyToClipBoard';
import { CardStyled as Card, HelpTitle } from './styles';
function renderHeadingClipboardSegments(title: string, text: string): React.ReactNode {
return (
<Fragment>
<Typography variant={'body2'}>{title}</Typography>
<CopyToClipBoard text={text} />
</Fragment>
);
}
const Help: React.FC = () => {
const registryUrl = getRegistryURL();
return (
<Card id="help-card">
<CardContent>
<Typography component="h2" gutterBottom={true} id="help-card__title" variant="headline">
{'No Package Published Yet.'}
</Typography>
<HelpTitle color="textSecondary" gutterBottom={true}>
{'To publish your first package just:'}
</HelpTitle>
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
<Typography variant="body2">{'3. Refresh this page.'}</Typography>
</CardContent>
<CardActions>
<Button color="primary" href="https://verdaccio.org/docs/en/installation" size="small" target="_blank">
{'Learn More'}
</Button>
</CardActions>
</Card>
);
};
export default Help;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Help /> component should render the component in default state 1`] = `"<div class=\\"MuiPaper-root-2 MuiPaper-elevation1-5 MuiPaper-rounded-3 MuiCard-root-1 css-ryznli e1wgaou60\\" id=\\"help-card\\"><div class=\\"MuiCardContent-root-29\\"><h2 class=\\"MuiTypography-root-30 MuiTypography-headline-35 MuiTypography-gutterBottom-57\\" id=\\"help-card__title\\">No Package Published Yet.</h2><p class=\\"MuiTypography-root-30 MuiTypography-body1-39 MuiTypography-colorTextSecondary-63 MuiTypography-gutterBottom-57 css-zg2fwz e1wgaou61\\">To publish your first package just:</p><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">1. Login</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">npm adduser --registry http://localhost</span><button class=\\"MuiButtonBase-root-80 MuiIconButton-root-74 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-79\\"><svg class=\\"MuiSvgIcon-root-83\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">2. Publish</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">npm publish --registry http://localhost</span><button class=\\"MuiButtonBase-root-80 MuiIconButton-root-74 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-79\\"><svg class=\\"MuiSvgIcon-root-83\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">3. Refresh this page.</p></div><div class=\\"MuiCardActions-root-92\\"><a class=\\"MuiButtonBase-root-80 MuiButton-root-95 MuiButton-text-97 MuiButton-textPrimary-98 MuiButton-flat-100 MuiButton-flatPrimary-101 MuiButton-sizeSmall-118 MuiCardActions-action-94\\" tabindex=\\"0\\" role=\\"button\\" href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\"><span class=\\"MuiButton-label-96\\">Learn More</span></a></div></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Help';

View File

@@ -0,0 +1,16 @@
import Card from '@material-ui/core/Card';
import Typography from '@material-ui/core/Typography';
import styled from 'react-emotion';
export const CardStyled = styled(Card)`
&& {
width: 600px;
margin: auto;
}
`;
export const HelpTitle = styled(Typography)`
&& {
margin-bottom: 20px;
}
`;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import Icon from './Icon';
describe('<Icon /> component', () => {
const props = {
name: 'austria',
};
test('should render the component in default state', () => {
const wrapper = shallow(<Icon name={props.name} />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,82 @@
import React, { MouseEvent } from 'react';
import capitalize from 'lodash/capitalize';
import { Svg, Img, ImgWrapper } from './styles';
import brazil from './img/brazil.svg';
import china from './img/china.svg';
import india from './img/india.svg';
import nicaragua from './img/nicaragua.svg';
import pakistan from './img/pakistan.svg';
import austria from './img/austria.svg';
import spain from './img/spain.svg';
import earth from './img/earth.svg';
import verdaccio from './img/verdaccio.svg';
import filebinary from './img/filebinary.svg';
import law from './img/law.svg';
import license from './img/license.svg';
import time from './img/time.svg';
import version from './img/version.svg';
export interface IconsMap {
brazil: string;
spain: string;
china: string;
nicaragua: string;
pakistan: string;
austria: string;
india: string;
earth: string;
verdaccio: string;
license: string;
time: string;
law: string;
version: string;
filebinary: string;
[key: string]: string;
}
export const Icons: IconsMap = {
brazil,
spain,
china,
nicaragua,
pakistan,
india,
austria,
earth,
verdaccio,
filebinary,
law,
license,
time,
version,
};
export interface Props {
name: keyof IconsMap;
className?: string;
onClick?: (event: MouseEvent<SVGElement | HTMLSpanElement>) => void;
size?: 'sm' | 'md';
pointer?: boolean;
img?: boolean;
modifiers?: any;
}
const Icon: React.FC<Props> = ({ className, name, size = 'sm', img = false, pointer = false, ...props }) => {
// @ts-ignore
const title = capitalize(name);
return img ? (
<ImgWrapper className={className} name={name} pointer={pointer} size={size} title={title} {...props}>
<Img alt={title} src={Icons[name]} />
</ImgWrapper>
) : (
// @ts-ignore
<Svg className={className} pointer={pointer} size={size} {...props}>
<title>{title}</title>
<use xlinkHref={`${Icons[name]}#${name}`} />
</Svg>
);
};
export default Icon;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Icon /> component should render the component in default state 1`] = `"<svg class=\\"css-3skwlp ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg>"`;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="austria"><path d="M473.655 88.276H38.345C17.167 88.276 0 105.443 0 126.621v73.471h512v-73.471c0-21.178-17.167-38.345-38.345-38.345zM0 385.379c0 21.177 17.167 38.345 38.345 38.345h435.31c21.177 0 38.345-17.167 38.345-38.345v-73.471H0v73.471z" fill="#ff4b55"/><path fill="#f5f5f5" d="M0 200.09h512V311.9H0z"/></svg>

After

Width:  |  Height:  |  Size: 380 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="brazil"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4V9z" fill="#009b3a"/><path d="M32.727 18L18 6.876 3.27 18 18 29.125 32.727 18z" fill="#fedf01"/><path d="M24.434 18.076a6.458 6.458 0 1 1-12.917 0 6.458 6.458 0 0 1 12.917 0" fill="#002776"/><path d="M12.277 21.113a6.406 6.406 0 0 1-.672-2.023c3.994.29 9.417-1.892 11.744-4.596.402.604.7 1.28.882 2.004-2.871 2.809-7.916 4.63-11.954 4.615" fill="#cbe9d4"/><path d="M13 16.767h-1v1h1v-1zM14 14.767h-1v1h1v-1z" fill="#88c9f9"/><path d="M16 16.767h-1v1h1v-1zM18 15.767h-1v1h1v-1zM22 13.767h-1v1h1v-1zM19 12.767h-1v1h1v-1zM22 18.767h-1v1h1v-1z" fill="#55acee"/><path d="M20 14.767h-1v1h1v-1z" fill="#3b88c3"/></g></svg>

After

Width:  |  Height:  |  Size: 898 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="china"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4V9z" fill="#de2910"/><path d="M7 25.049l.929-2.67 2.826-.06-2.253-1.706.819-2.707L7 19.52l-2.321-1.615.819 2.707-2.253 1.707 2.826.059.929 2.67zM13 28.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM15 24.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM15 20.472l.34-.688.759-.11-.549-.536.129-.756-.679.357-.679-.357.13.756-.55.536.76.11.339.688zM13 16.473l.34-.69.759-.11-.549-.534.129-.757-.679.357-.679-.357.13.757-.55.535.76.11.339.689z" fill="#ffde02"/></g></svg>

After

Width:  |  Height:  |  Size: 833 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="earth"><Title>Earth</Title><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath><clipPath id="b"><path d="M18 36C8.059 36 0 27.941 0 18S8.059 0 18 0s18 8.059 18 18-8.059 18-18 18z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 18c0-9.941-8.059-18-18-18S0 8.059 0 18s8.059 18 18 18 18-8.059 18-18" fill="#88c9f9"/></g><g clip-path="url(#b)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M3.627 28.952c-.45 2.93 2.195 4.156 3.607 4.47 1.412.314 2.776.62 2.933-.006.156-.628.311-1.46 1.173-1.148.862.314 3.043.56 4.063 1.342 1.02.783 2.244.787 3.264.473 1.02-.313 3.877-.227 3.25-1.167-.627-.94-1.825-.827-2.45-1.924-.628-1.099.171-1.826 1.033-1.826.865 0 1.71-.135 2.26.727.548.863-.383 2.463.324 2.357.706-.106 1.477-.866 2.03-2.043.547-1.176 1.408-.47 1.723-1.176.313-.705 2.04-2.039 1.177-1.804-.864.236-1.726.392-1.96-.47-.237-.863.388-1.726-.237-1.647-.627.08-.86-.089-1.725-.004-.862.083-1.333.631-2.039-.545-.705-1.175-1.254-1.96-1.567-2.509-.315-.549-.785-.86-.55-1.96.235-1.099-.628-.785-.628.156 0 .94-.548 1.098-1.253.942-.706-.157-1.803-.313-1.724-1.098.077-.784-.315-1.725.313-2.352.627-.629 1.33.076 1.723-.158.393-.237 1.525-.023 1.133-.416-.393-.39-1.76-.88-.976-1.509a4.831 4.831 0 0 1 1.893-.907c.313-.08.062.774 1.083 1.166 1.017.392 2.608 1.29 3 .584.391-.705.338-.595 1.75-.75 1.41-.156 1.79-.585 2.417-1.917.626-1.333.446-1.192 1.462-1.58 1.021-.394 1.678-.223.737-1.087-.94-.86-1.65-.814-2.199-1.833-.55-1.017-.153-1.73-1.25-2.75A20.755 20.755 0 0 0 24 4c-.618-.37-2.162-2.07-3.083-2.667-.834-.54-1.083 0-1.083 0s.256 1.667.964 2.372c.704.705 1.105 3.344.87 4.128-.235.783-1.36 1.02-1.75 1.333-.393.312-1.418 1.548-1.418 2.334 0 .784 1.71 2.81 1.71 2.81.218-1.089-1.039.328-1.627.523-.47.157-1.542 1.656-2.459 1.814-.916.16-1.363.7-2.068 1.25-.706.55-2.43 1.332-2.353 2.195.08.862-1.725 1.568-2.038 1.568-.314 0-1.019 0-1.647 1.098-.627 1.098-1.725 2.196-1.41 2.98.312.783.391 1.726.233 2.588-.156.862-1.332 1.176-1.567.941-.235-.236-1.489-1.335-1.647-.315" fill="#5c913b"/></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="filebinary">
<path d="M8.5 1H1c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V4.5L8.5 1zM11 14H1V2h7l3 3v9zM5 6.98L3.5 8.5 5 10l-.5 1L2 8.5 4.5 6l.5.98zM7.5 6L10 8.5 7.5 11l-.5-.98L8.5 8.5 7 7l.5-1z"></path>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="india"><Title>India</Title><path d="M0 384c0 31.418 25.473 56.889 56.889 56.889H455.11C486.53 440.889 512 415.416 512 384v-56.889H0V384z" fill="#138808"/><path d="M0 327.111h512V184.89H0V327.11z" fill="#eee"/><path d="M512 184.889V128c0-31.417-25.473-56.889-56.889-56.889H56.89C25.472 71.111 0 96.583 0 128v56.889h512z" fill="#f93"/><path d="M312.889 256c0-31.431-25.473-56.902-56.903-56.902-31.417 0-56.888 25.472-56.888 56.902 0 31.418 25.472 56.889 56.888 56.889 31.432 0 56.903-25.472 56.903-56.889" fill="navy"/><path d="M298.666 256c0-23.566-19.115-42.681-42.681-42.681S213.319 232.434 213.319 256s19.1 42.666 42.666 42.666 42.681-19.1 42.681-42.666" fill="#eee"/><path d="M256 213.334l2.076 32.199 14.251-28.943-10.396 30.535 24.235-21.305-21.291 24.249 30.535-10.396-28.942 14.25L298.666 256l-32.198 2.076 28.942 14.237-30.535-10.383 21.291 24.235-24.235-21.29 10.396 30.535-14.25-28.942L256 298.666l-2.076-32.198-14.252 28.942 10.397-30.535-24.249 21.291 21.305-24.235-30.535 10.383 28.942-14.236L213.334 256l32.199-2.076-28.943-14.251 30.535 10.396-21.305-24.249 24.249 21.305-10.396-30.535 14.25 28.943 2.077-32.2z" fill="navy" opacity=".6"/><path d="M241.778 256c0-7.851 6.37-14.223 14.222-14.223s14.223 6.372 14.223 14.223-6.372 14.223-14.223 14.223-14.223-6.372-14.223-14.223" fill="navy"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" id="law">
<path fill-rule="evenodd" d="M7 4c-.83 0-1.5-.67-1.5-1.5S6.17 1 7 1s1.5.67 1.5 1.5S7.83 4 7 4zm7 6c0 1.11-.89 2-2 2h-1c-1.11 0-2-.89-2-2l2-4h-1c-.55 0-1-.45-1-1H8v8c.42 0 1 .45 1 1h1c.42 0 1 .45 1 1H3c0-.55.58-1 1-1h1c0-.55.58-1 1-1h.03L6 5H5c0 .55-.45 1-1 1H3l2 4c0 1.11-.89 2-2 2H2c-1.11 0-2-.89-2-2l2-4H1V5h3c0-.55.45-1 1-1h4c.55 0 1 .45 1 1h3v1h-1l2 4zM2.5 7L1 10h3L2.5 7zM13 10l-1.5-3-1.5 3h3z"></path>
</svg>

After

Width:  |  Height:  |  Size: 464 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 490.652 490.652" id="license"><path d="M456.607 9.904H34.04C15.269 9.904 0 25.17 0 43.945V310.17c0 18.77 15.269 34.039 34.04 34.039h260.642c-4.043-10.35-6.534-21.737-7.159-33.742l-253.974-.297.491-266.718 423.063.493V310.17c0 .279-.23.492-.495.492l-15.251-.019c-.637 11.942-3.111 23.263-7.138 33.565h22.389c18.777 0 34.045-15.27 34.045-34.039V43.945c-.001-18.775-15.268-34.041-34.046-34.041z"/><path d="M364.447 381.02c33.354 0 60.386-34.152 60.386-76.289 0-42.131-27.031-76.284-60.386-76.284-33.35 0-60.38 34.153-60.38 76.284 0 42.137 27.031 76.289 60.38 76.289zM81.087 118.345h149.561c9.273 0 16.776-7.499 16.776-16.772 0-9.271-7.504-16.775-16.776-16.775H81.087c-9.271 0-16.771 7.504-16.771 16.775 0 9.273 7.499 16.772 16.771 16.772zM415.167 177.061c0-9.273-7.505-16.779-16.776-16.779H81.087c-9.271 0-16.771 7.506-16.771 16.779 0 9.271 7.499 16.77 16.771 16.77h317.304c9.271 0 16.776-7.498 16.776-16.77zM81.087 235.768c-9.271 0-16.771 7.504-16.771 16.771 0 9.273 7.499 16.777 16.771 16.777h149.561c9.273 0 16.776-7.504 16.776-16.777 0-9.268-7.504-16.771-16.776-16.771H81.087zM415.425 374.452c-.065.082-23.295 26.869-55.728 23.081l40.331 81.577a2.93 2.93 0 0 0 2.637 1.639h.215a2.95 2.95 0 0 0 2.571-2.031l10.174-31.234a3.024 3.024 0 0 1 1.488-1.728 3.07 3.07 0 0 1 2.28-.13l30.977 10.88c.324.112.651.163.98.163.836 0 1.623-.348 2.195-.982.803-.9.964-2.18.441-3.244l-38.561-77.991zM313.653 374.598c-.049-.047-.118-.096-.185-.146l-38.561 77.973a2.953 2.953 0 0 0 .448 3.262 2.961 2.961 0 0 0 3.174.819l30.978-10.88c.328-.113.658-.16.982-.16.442 0 .884.096 1.294.29.706.36 1.245.987 1.493 1.741l10.173 31.221c.378 1.133 1.391 1.936 2.572 2.014h.21a2.95 2.95 0 0 0 2.642-1.621l26.206-53.01-16.755-33.893c-.066-.034-14.672-5.884-24.671-17.61z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="nicaragua"><Title>Nicaragua</Title><path d="M512 384c0 31.418-25.473 56.889-56.889 56.889H56.89C25.472 440.889 0 415.417 0 384V128c0-31.418 25.472-56.889 56.889-56.889H455.11C486.53 71.111 512 96.584 512 128v256z" fill="#265fb5"/><path d="M512 327.111H0V184.89h512V327.11z" fill="#eee"/><path d="M320.811 256c0 35.797-29.014 64.811-64.811 64.811-35.783 0-64.811-29.014-64.811-64.811s29.027-64.811 64.811-64.811c35.797 0 64.811 29.013 64.811 64.811" fill="#a9bf4c"/><path d="M312.889 256c0 31.418-25.473 56.889-56.889 56.889S199.111 287.416 199.111 256s25.473-56.889 56.889-56.889 56.889 25.471 56.889 56.889" fill="#eee"/><path d="M209.891 286.649l45.909-79.517 45.909 79.517H209.89z" fill="#265fb5"/><path d="M215.04 283.591l40.818-70.685 40.803 70.685H215.04z" fill="#55acee"/><path d="M215.04 283.591l9.841-17.052 61.483-.783 10.297 17.835H215.04z" fill="#bbddf5"/><path d="M222.891 272.441l15.331-12.445 6.67 7.553 5.774-6.215 4.893 4.892 4.665-5.12 5.561 5.12 5.106-5.334 4.665 5.334 4.451-4.892 7.325 9.102 1.338 2.674s-7.78 1.55-18.446.669c-10.667-.896-16.882 1.55-25.33 2.66-8.448 1.109-23.553-1.109-23.553-1.109l1.55-2.889z" fill="#5c913b"/><g fill="#e2f09f"><path d="M237.995 262.67l10.226 11.107-5.12.442-5.774-8.434.668-3.115zM250.226 263.553l8.22 7.338-3.782.654-3.996-5.546-.442-2.446zM260.665 263.111l7.111 6.67L266 270.89l-5.774-6.229.44-1.55zM269.995 263.111l-2.888 2.66 1.338 1.565 1.55-4.225zM278.884 263.339l-3.328 4.224 1.109 1.99 2.446-4.664-.227-1.55z"/></g><path d="M256.426 233.671l.939 13.227 7.566-10.867-5.675 11.805 11.805-5.66-10.851 7.553 13.213.939-13.213.952 10.851 7.553-11.805-5.66 5.675 11.805-7.566-10.867-.939 13.226-.939-13.226-7.566 10.867 5.675-11.805-11.805 5.66 10.851-7.553-13.212-.953 13.212-.938-10.85-7.553 11.804 5.66-5.675-11.805 7.566 10.866.94-13.226z" fill="#bbddf5"/><path d="M256 244.665l-2.66 2.66s.654 4.011.441 4.679C253.554 252.658 256 256 256 256l3.327-3.996-.88-5.334-2.447-2.005z" fill="#dd2e44"/><path d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-3.996-7.111s-6.443-4.893-13.995-4.893c-7.567 0-16 6.001-16 6.001l-3.783 7.553s9.771-6.884 20.665-6.884" fill="#269"/><path d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.888-5.106s-7.78-4.665-15.331-4.665-16.896 5.987-16.896 5.987l-2.66 5.334c.001 0 9.772-6.884 20.666-6.884" fill="#ffcc4d"/><path d="M257.28 240.071c10.894 0 17.109 5.334 17.109 5.334l-2.005-3.327s-5.988-4.224-16.214-3.783c-7.553 0-18.005 5.561-18.005 5.561l-1.55 3.1c0-.001 9.771-6.885 20.665-6.885" fill="#dd2e44"/><g fill="#eee"><path d="M264.291 322.873h-14.165V309.29h14.165v13.582zM247.865 203.975h-10.183l-5.106-12.459 10.198-1.137 5.091 13.596zM271.075 209.635l-6.784-2.261 3.385-16.426 13.028 1.137-9.629 17.55zM209.934 279.281l-19.811 4.523-.57-13.583 19.812-2.83.569 11.89zM215.04 293.433l-16.426 6.812-3.954-10.766 15.289-5.106 5.091 9.06zM324.309 280.989l-18.105-5.091v-9.63l20.366-2.83-2.261 17.55zM315.306 299.663l-16.981-7.937 5.646-10.182 18.703 5.66-7.368 12.459z"/></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="pakistan"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M27.61 20.952l-1.213-2.022-.208 2.349-2.298.528 2.17.924-.207 2.349 1.548-1.78 2.17.925-1.212-2.023 1.548-1.78-2.298.53zM22.5 10.528a7.5 7.5 0 0 0-7.5 7.5c0 3.72 2.711 6.798 6.263 7.389a6.494 6.494 0 0 1-3.763-5.89 6.5 6.5 0 0 1 6.5-6.5 6.494 6.494 0 0 1 5.89 3.764c-.592-3.552-3.67-6.263-7.39-6.263M32 31H9V5h23a4 4 0 0 1 4 4v18a4 4 0 0 1-4 4" fill="#004600"/><path d="M4 31a4 4 0 0 1-4-4V9a4 4 0 0 1 4-4h5v26H4z" fill="#eee"/><path d="M29.572 24.225l-2.17-.924-1.548 1.779.207-2.35-2.17-.922 2.298-.528.208-2.35 1.213 2.022 2.298-.53-1.548 1.78 1.212 2.023z" fill="#fff"/><path d="M24 13.028a6.496 6.496 0 1 0-2.737 12.39c-3.552-.591-6.263-3.67-6.263-7.39a7.5 7.5 0 0 1 7.5-7.5c3.72 0 6.799 2.71 7.39 6.263A6.496 6.496 0 0 0 24 13.028" fill="#fff"/></g></svg>

After

Width:  |  Height:  |  Size: 977 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 45" id="spain"><defs><clipPath id="a"><path d="M0 36h36V0H0v36z"/></clipPath></defs><g clip-path="url(#a)" transform="matrix(1.25 0 0 -1.25 0 45)"><path d="M36 9a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h28a4 4 0 0 0 4-4V9z" fill="#c60a1d"/><path d="M36 12H0v12h36V12z" fill="#ffc400"/><path d="M9 19v-3a3 3 0 1 1 6 0v3H9z" fill="#ea596e"/><path d="M12 17h3v3h-3v-3z" fill="#f4a2b2"/><path d="M12 17H9v3h3v-3z" fill="#dd2e44"/><path d="M15 21.5c0-.829-1.343-1.5-3-1.5s-3 .671-3 1.5 1.343 1.5 3 1.5 3-.671 3-1.5" fill="#ea596e"/><path d="M15 22.25c0 .414-1.343.75-3 .75s-3-.336-3-.75 1.343-.75 3-.75 3 .336 3 .75" fill="#ffac33"/><path d="M7 13h1v7H7v-7zM17 13h-1v7h1v-7z" fill="#99aab5"/><path d="M9 13H6v1h3v-1zM18 13h-3v1h3v-1zM8 20H7v1h1v-1zM17 20h-1v1h1v-1z" fill="#66757f"/></g></svg>

After

Width:  |  Height:  |  Size: 851 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="time"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M0 0h24v24H0z" fill="none"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@@ -0,0 +1 @@
<svg width='100px' height='100px' xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="verdaccio"><defs><path d="M48 17.6L32.8 48H24L.4.8h15.2l12.8 25.6 4.4-8.8H48z" id="a"/><filter id="b" filterUnits="objectBoundingBox" height="140.3%" width="139.9%" y="-11.7%" x="-20%"><feGaussianBlur stdDeviation="2.5"/></filter><path d="M50.8 12H35.6L41.2.8h15.2L50.8 12z" id="c"/><filter id="d" filterUnits="objectBoundingBox" height="269.6%" width="191.3%" y="-49.1%" x="-45.7%"><feGaussianBlur stdDeviation="2.5"/></filter><path d="M32.8 48H24L.4.8h15.2l20.377 40.89L32.8 48z" id="e"/></defs><path fill="none" d="M-1-1h582v402H-1z"/><g stroke="null" fill-rule="evenodd" fill="none"><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#a" filter="url(#b)" fill="#000"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#a" fill="#405236"/><path stroke="#405236" d="M80.27 40.4H58.816L50 58.028 26.785 11.6H5.33l38.4 76.8h12.542l24-48z" stroke-width="2.4"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#c" filter="url(#d)" fill="#000"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#c" fill="#CD4000"/><path stroke="#CD4000" d="M87.128 26.686L94.671 11.6H73.215l-7.543 15.086h21.456z" stroke-width="2.4"/><use transform="translate(-37.027 -40.362) scale(1.71429)" x="22.366" y="28.311" xlink:href="#e" fill="#4A5E3F"/><path stroke="#405236" d="M56.274 88.4l4.415-8.763L26.783 11.6H5.33l38.4 76.8h12.547-.002z" stroke-width="2.4"/><path stroke="#CD4000" stroke-linecap="square" stroke-width="2.4" d="M65.771 11.6h26.094m-32.95 6.857h26.092m-32.95 8.229H78.15"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" height="16" width="14" id="version">
<path fill-rule="evenodd" d="M13 3H7c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 8H8V5h4v6zM4 4h1v1H4v6h1v1H4c-.55 0-1-.45-1-1V5c0-.55.45-1 1-1zM1 5h1v1H1v4h1v1H1c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1z"></path>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1 @@
export { default } from './Icon';

View File

@@ -0,0 +1,44 @@
import styled, { css } from 'react-emotion';
const getSize = (size?: 'md' | 'sm') => {
switch (size) {
case 'md':
return `
width: 18px;
height: 18px;
`;
default:
return `
width: 14px;
height: 16px;
`;
}
};
const commonStyle = ({ size = 'sm', pointer, modifiers }: any) => css`
&& {
display: inline-block;
cursor: ${pointer ? 'pointer' : 'default'};
${getSize(size)};
${modifiers && modifiers};
}
`;
export const Svg = styled('svg')`
&& {
${commonStyle};
}
`;
export const ImgWrapper = styled('span')`
&& {
${commonStyle};
}
`;
export const Img = styled('img')`
&& {
width: 100%;
height: auto;
}
`;

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import Install from './Install';
describe('<Install /> component', () => {
test('should render the component in default state', () => {
const wrapper = shallow(<Install />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,55 @@
import List from '@material-ui/core/List';
import ListItemText from '@material-ui/core/ListItemText';
import React, { Component } from 'react';
// @ts-ignore
import { DetailContextConsumer } from '../../pages/version/Version';
import CopyToClipBoard from '../CopyToClipBoard';
// logos of package managers
import npm from './img/npm.svg';
import pnpm from './img/pnpm.svg';
import yarn from './img/yarn.svg';
import { Heading, InstallItem, PackageMangerAvatar } from './styles';
class Install extends Component {
public render() {
return (
<DetailContextConsumer>
{(context: any) => {
return context && context.packageName && this.renderCopyCLI(context);
}}
</DetailContextConsumer>
);
}
public renderCopyCLI = ({ packageName }: { packageName: string }) => {
return (
<>
<List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List>
</>
);
};
public renderListItems = (packageName: string) => {
return (
<>
<InstallItem>
<PackageMangerAvatar alt={'npm logo'} src={npm} />
<ListItemText primary={<CopyToClipBoard text={`npm install ${packageName}`} />} secondary={'Install using NPM'} />
</InstallItem>
<InstallItem>
<PackageMangerAvatar alt={'yarn logo'} src={yarn} />
<ListItemText primary={<CopyToClipBoard text={`yarn add ${packageName}`} />} secondary={'Install using Yarn'} />
</InstallItem>
<InstallItem>
<PackageMangerAvatar alt={'pnpm logo'} src={pnpm} />
<ListItemText primary={<CopyToClipBoard text={`pnpm install ${packageName}`} />} secondary={'Install using PNPM'} />
</InstallItem>
</>
);
};
}
export default Install;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Install /> component should render the component in default state 1`] = `""`;

View File

@@ -0,0 +1,15 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
<polygon fill="#CC0000" points="23,65.6 130,3.3 237,65.6 237,190.1 130,252.4 23,190.1 "/>
<polygon fill="#FFFFFF" points="133,127.8 232.5,70.5 236.5,186 133,248.5 "/>
<g>
<path fill="#CC0000" d="M234,67l-0.3,122.4l-103.8,60.2l-0.5-120.3L234,67z M146.7,139.3l0.3,80.4l34.5-20.1l-0.1-60.6l17.4-10.3
l0,60.8l17.4-10.2l0.1-81.4L146.7,139.3z"/>
</g>
<path fill="#910505" d="M136.8,4.2c-4.8-2.7-12.5-2.7-17.3,0L24.7,58.7c-4.8,2.7-8.6,9.5-8.6,14.9v109c0,5.5,3.9,12.2,8.6,14.9
l94.8,54.5c4.8,2.7,12.5,2.7,17.3,0l94.8-54.5c4.8-2.7,8.6-9.5,8.6-14.9v-109c0-5.5-3.9-12.2-8.6-14.9L136.8,4.2z M220.9,61.2
c4.8,2.7,4.8,7.2,0,9.9l-83,47.7c-4.8,2.7-12.5,2.7-17.3,0L36.4,70.4c-4.8-2.7-4.8-7.2,0-9.9l83-47.7c4.8-2.7,12.5-2.7,17.3,0
L220.9,61.2z M23.5,81.5c0-5.5,3.9-7.7,8.6-5l84.9,48.8c4.8,2.7,8.6,9.5,8.6,14.9V237c0,5.5-3.9,7.7-8.6,5l-84.9-48.8
c-4.8-2.7-8.6-9.5-8.6-14.9V81.5z M141.8,240.5c-4.8,2.7-8.6,0.5-8.6-5v-95.3c0-5.5,3.9-12.2,8.6-14.9L224.2,78
c4.8-2.7,8.6-0.5,8.6,5v95.3c0,5.5-3.9,12.2-8.6,14.9L141.8,240.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="66.09157809474142 33.5 184.5 184.49999999999997" width="180" height="180"><defs><path d="M67.59 35L247.59 35L247.59 215L67.59 215L67.59 35Z" id="b2JZZcA3fT"></path><path d="M237.6 95L187.6 95L187.6 45L237.6 45L237.6 95Z" id="bj0tb0Y8q"></path><path d="M182.59 95L132.59 95L132.59 45L182.59 45L182.59 95Z" id="dkDSTzPj3"></path><path d="M127.59 95L77.59 95L77.59 45L127.59 45L127.59 95Z" id="a4vNdcNLpF"></path><path d="M237.6 150L187.6 150L187.6 100L237.6 100L237.6 150Z" id="h2t4Zj1jSU"></path><path d="M182.59 150L132.59 150L132.59 100L182.59 100L182.59 150Z" id="b4t5pngwvT"></path><path d="M182.59 205L132.59 205L132.59 155L182.59 155L182.59 205Z" id="b9s1gd5m2"></path><path d="M237.6 205L187.6 205L187.6 155L237.6 155L237.6 205Z" id="cmt9WLvz7"></path><path d="M127.59 205L77.59 205L77.59 155L127.59 155L127.59 205Z" id="bJUNqgFSg"></path></defs><g><g><use xlink:href="#b2JZZcA3fT" opacity="1" fill="#ffffff" fill-opacity="1"></use></g><g><use xlink:href="#bj0tb0Y8q" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#dkDSTzPj3" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#a4vNdcNLpF" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#h2t4Zj1jSU" opacity="1" fill="#f9ad00" fill-opacity="1"></use></g><g><use xlink:href="#b4t5pngwvT" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#b9s1gd5m2" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#cmt9WLvz7" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g><g><use xlink:href="#bJUNqgFSg" opacity="1" fill="#4e4e4e" fill-opacity="1"></use></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 518 518"><style>.st0{fill:#2c8ebb}.st1{fill:#fff}</style><path class="st0" d="M259 0c143 0 259 116 259 259S402 518 259 518 0 402 0 259 116 0 259 0z"/><path class="st1" d="M435.2 337.5c-1.8-14.2-13.8-24-29.2-23.8-23 .3-42.3 12.2-55.1 20.1-5 3.1-9.3 5.4-13 7.1.8-11.6.1-26.8-5.9-43.5-7.3-20-17.1-32.3-24.1-39.4 8.1-11.8 19.2-29 24.4-55.6 4.5-22.7 3.1-58-7.2-77.8-2.1-4-5.6-6.9-10-8.1-1.8-.5-5.2-1.5-11.9.4C293.1 96 289.6 93.8 286.9 92c-5.6-3.6-12.2-4.4-18.4-2.1-8.3 3-15.4 11-22.1 25.2-1 2.1-1.9 4.1-2.7 6.1-12.7.9-32.7 5.5-49.6 23.8-2.1 2.3-6.2 4-10.5 5.6h.1c-8.8 3.1-12.8 10.3-17.7 23.3-6.8 18.2.2 36.1 7.1 47.7-9.4 8.4-21.9 21.8-28.5 37.5-8.2 19.4-9.1 38.4-8.8 48.7-7 7.4-17.8 21.3-19 36.9-1.6 21.8 6.3 36.6 9.8 42 1 1.6 2.1 2.9 3.3 4.2-.4 2.7-.5 5.6.1 8.6 1.3 7 5.7 12.7 12.4 16.3 13.2 7 31.6 10 45.8 2.9 5.1 5.4 14.4 10.6 31.3 10.6h1c4.3 0 58.9-2.9 74.8-6.8 7.1-1.7 12-4.7 15.2-7.4 10.2-3.2 38.4-12.8 65-30 18.8-12.2 25.3-14.8 39.3-18.2 13.6-3.3 22.1-15.7 20.4-29.4zm-23.8 14.7c-16 3.8-24.1 7.3-43.9 20.2-30.9 20-64.7 29.3-64.7 29.3s-2.8 4.2-10.9 6.1c-14 3.4-66.7 6.3-71.5 6.4-12.9.1-20.8-3.3-23-8.6-6.7-16 9.6-23 9.6-23s-3.6-2.2-5.7-4.2c-1.9-1.9-3.9-5.7-4.5-4.3-2.5 6.1-3.8 21-10.5 27.7-9.2 9.3-26.6 6.2-36.9.8-11.3-6 .8-20.1.8-20.1s-6.1 3.6-11-3.8c-4.4-6.8-8.5-18.4-7.4-32.7 1.2-16.3 19.4-32.1 19.4-32.1s-3.2-24.1 7.3-48.8c9.5-22.5 35.1-40.6 35.1-40.6s-21.5-23.8-13.5-45.2c5.2-14 7.3-13.9 9-14.5 6-2.3 11.8-4.8 16.1-9.5 21.5-23.2 48.9-18.8 48.9-18.8s13-39.5 25-31.8c3.7 2.4 17 32 17 32s14.2-8.3 15.8-5.2c8.6 16.7 9.6 48.6 5.8 68-6.4 32-22.4 49.2-28.8 60-1.5 2.5 17.2 10.4 29 43.1 10.9 29.9 1.2 55 2.9 57.8.3.5.4.7.4.7s12.5 1 37.6-14.5c13.4-8.3 29.3-17.6 47.4-17.8 17.5-.3 18.4 20.2 5.2 23.4z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
export { default } from './Install';

View File

@@ -0,0 +1,23 @@
import Avatar from '@material-ui/core/Avatar';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
import styled from 'react-emotion';
export const Heading = styled(Typography)`
&& {
font-weight: 700;
text-transform: capitalize;
}
`;
export const InstallItem = styled(ListItem)`
&& {
padding: 0;
}
`;
export const PackageMangerAvatar = styled(Avatar)`
&& {
border-radius: 0px;
}
`;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import Label from './Label';
describe('<Label /> component', () => {
const props = {
text: 'test',
};
test('should render the component in default state', () => {
const wrapper = shallow(<Label text={props.text} />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,30 @@
import React from 'react';
import styled from 'react-emotion';
import { fontWeight } from '../../utils/styles/sizes';
interface Props {
text: string;
capitalize?: boolean;
weight?: string;
modifiers?: any;
}
const Wrapper = styled('div')`
font-weight: ${({ weight }) => {
// @ts-ignore
return fontWeight[weight];
}};
text-transform: ${({ capitalize }) => (capitalize ? 'capitalize' : 'none')};
${({ modifiers }: Props) => modifiers && modifiers};
`;
const Label: React.FC<Props> = ({ text = '', capitalize = false, weight = 'regular', ...props }) => {
return (
// @ts-ignore
<Wrapper capitalize={capitalize} weight={weight} {...props}>
{text}
</Wrapper>
);
};
export default Label;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Label /> component should render the component in default state 1`] = `"<div class=\\"css-1xfhjjm esyufg60\\">test</div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Label';

View File

@@ -0,0 +1,27 @@
import styled, { css } from 'react-emotion';
export const Content = styled('div')`
&& {
background-color: #ffffff;
flex: 1;
display: flex;
position: relative;
flex-direction: column;
}
`;
export const Container = styled('div')`
&& {
display: flex;
flex-direction: column;
min-height: 100vh;
overflow: hidden;
${props =>
// @ts-ignore
props.isLoading &&
css`
${Content} {
background-color: #f5f6f8;
}
`}
`;

View File

@@ -0,0 +1 @@
export { Content, Container } from './Layout';

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import Link from './Link';
describe('<Link /> component', () => {
const props = {
to: 'https://github.com/verdaccio/ui',
};
test('should render the component in default state', () => {
const wrapper = shallow(<Link blank={true} to={props.to} />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,15 @@
import React, { ReactNode } from 'react';
interface Props {
children?: ReactNode;
to: string;
blank?: boolean;
}
const Link: React.FC<Props> = ({ children, to = '#', blank = false, ...props }) => (
<a href={to} target={blank ? '_blank' : '_self'} {...props}>
{children}
</a>
);
export default Link;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Link /> component should render the component in default state 1`] = `"<a href=\\"https://github.com/verdaccio/ui\\" target=\\"_blank\\"></a>"`;

View File

@@ -0,0 +1 @@
export { default } from './Link';

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import Loading from './Loading';
describe('<Loading /> component', () => {
test('should render the component in default state', () => {
const wrapper = shallow(<Loading />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,17 @@
import React from 'react';
import Logo from '../Logo';
import Spinner from '../Spinner';
import { Wrapper, Badge } from './styles';
const Loading: React.FC = () => (
<Wrapper>
<Badge>
<Logo />
</Badge>
<Spinner />
</Wrapper>
);
export default Loading;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Loading /> component should render the component in default state 1`] = `"<div class=\\"css-1221txa eimgwje0\\"><div class=\\"css-bxochs eimgwje1\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></div><div class=\\"css-vqrgi e1ag4h8b0\\"><div class=\\"MuiCircularProgress-root-1 MuiCircularProgress-colorPrimary-4 MuiCircularProgress-indeterminate-3 css-15gl0ho e1ag4h8b1\\" style=\\"width:50px;height:50px\\" role=\\"progressbar\\"><svg class=\\"MuiCircularProgress-svg-6\\" viewBox=\\"22 22 44 44\\"><circle class=\\"MuiCircularProgress-circle-7 MuiCircularProgress-circleIndeterminate-9\\" cx=\\"44\\" cy=\\"44\\" r=\\"20.2\\" fill=\\"none\\" stroke-width=\\"3.6\\"></circle></svg></div></div></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Loading';

View File

@@ -0,0 +1,19 @@
import styled from 'react-emotion';
export const Wrapper = styled('div')`
&& {
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
position: absolute;
}
`;
export const Badge = styled('div')`
&& {
margin: 0 0 30px 0;
border-radius: 25px;
box-shadow: 0 10px 20px 0 rgba(69, 58, 100, 0.2);
background: #f7f8f6;
}
`;

View File

@@ -0,0 +1,128 @@
/**
* @prettier
*/
import React from 'react';
import { mount } from 'enzyme';
import LoginModal from './Login';
const eventUsername = {
target: {
value: 'xyz',
},
};
const eventPassword = {
target: {
value: '1234',
},
};
const event = {
preventDefault: jest.fn(),
};
describe('<LoginModal />', () => {
test('should load the component in default state', () => {
const wrapper = mount(<LoginModal />);
expect(wrapper.html()).toMatchSnapshot();
});
test('should load the component with props', () => {
const props = {
visibility: true,
error: {
type: 'error',
title: 'Error Title',
description: 'Error Description',
},
onCancel: () => {},
onSubmit: () => {},
};
const wrapper = mount(<LoginModal {...props} />);
expect(wrapper.html()).toMatchSnapshot();
});
test('onCancel: should close the login modal', () => {
const props = {
visibility: true,
error: {
type: 'error',
title: 'Error Title',
description: 'Error Description',
},
onCancel: jest.fn(),
onSubmit: () => {},
};
const wrapper = mount(<LoginModal {...props} />);
wrapper.find('button[id="login--form-cancel"]').simulate('click');
expect(props.onCancel).toHaveBeenCalled();
});
test('setCredentials - should set username and password in state', () => {
const props = {
visibility: true,
error: {},
onCancel: () => {},
onSubmit: () => {},
};
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
const { setCredentials } = wrapper.instance();
expect(setCredentials('username', eventUsername)).toBeUndefined();
expect(wrapper.state('form').username.value).toEqual('xyz');
expect(setCredentials('password', eventPassword)).toBeUndefined();
expect(wrapper.state('form').password.value).toEqual('1234');
});
test('validateCredentials: should validate credentials', async () => {
const props = {
visibility: true,
error: {},
onCancel: () => {},
onSubmit: jest.fn(),
};
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
const instance = wrapper.instance();
instance.submitCredentials = jest.fn();
const { validateCredentials, setCredentials, submitCredentials } = instance;
expect(setCredentials('username', eventUsername)).toBeUndefined();
expect(wrapper.state('form').username.value).toEqual('xyz');
expect(setCredentials('password', eventPassword)).toBeUndefined();
expect(wrapper.state('form').password.value).toEqual('1234');
validateCredentials(event);
expect(event.preventDefault).toHaveBeenCalled();
expect(wrapper.state('form').username.pristine).toEqual(false);
expect(wrapper.state('form').password.pristine).toEqual(false);
expect(submitCredentials).toHaveBeenCalledTimes(1);
});
test('submitCredentials: should submit credentials', async () => {
const props = {
onSubmit: jest.fn(),
};
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
const { setCredentials, submitCredentials } = wrapper.instance();
expect(setCredentials('username', eventUsername)).toBeUndefined();
expect(wrapper.state('form').username.value).toEqual('xyz');
expect(setCredentials('password', eventPassword)).toBeUndefined();
expect(wrapper.state('form').password.value).toEqual('1234');
await submitCredentials();
expect(props.onSubmit).toHaveBeenCalledWith('xyz', '1234');
expect(wrapper.state('form').username.value).toEqual('');
expect(wrapper.state('form').username.pristine).toEqual(true);
expect(wrapper.state('form').password.value).toEqual('');
expect(wrapper.state('form').password.pristine).toEqual(true);
});
});

View File

@@ -0,0 +1,203 @@
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';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import ErrorIcon from '@material-ui/icons/Error';
import InputLabel from '@material-ui/core/InputLabel';
import Input from '@material-ui/core/Input';
import FormControl from '@material-ui/core/FormControl';
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,
};
static defaultProps = {
error: {},
onCancel: () => {},
onSubmit: () => {},
visibility: true,
};
constructor(props) {
super(props);
this.state = {
form: {
username: {
required: true,
pristine: true,
helperText: 'Field required',
value: '',
},
password: {
required: true,
pristine: true,
helperText: 'Field required',
value: '',
},
},
error: props.error,
};
}
/**
* set login modal's username and password to current state
* Required to login
*/
setCredentials = (name, e) => {
const { form } = this.state;
this.setState({
form: {
...form,
[name]: {
...form[name],
value: e.target.value,
pristine: false,
},
},
});
};
setUsername = event => {
this.setCredentials('username', event);
};
setPassword = event => {
this.setCredentials('password', event);
};
validateCredentials = event => {
const { form } = this.state;
// prevents default submit behavior
event.preventDefault();
this.setState(
{
form: Object.keys(form).reduce(
(acc, key) => ({
...acc,
...{ [key]: { ...form[key], pristine: false } },
}),
{}
),
},
() => {
if (!Object.keys(form).some(id => !form[id])) {
this.submitCredentials();
}
}
);
};
submitCredentials = async () => {
const { form } = this.state;
const { onSubmit } = this.props;
await onSubmit(form.username.value, form.password.value);
// let's wait for API response and then set
// username and password filed to empty state
this.setState({
form: Object.keys(form).reduce(
(acc, key) => ({
...acc,
...{ [key]: { ...form[key], value: '', pristine: true } },
}),
{}
),
});
};
renderErrorMessage(title, description) {
return (
<span>
<div>
<strong>{title}</strong>
</div>
<div>{description}</div>
</span>
);
}
renderMessage(title, description) {
return (
<div className={classes.loginErrorMsg} id={'client-snackbar'}>
<ErrorIcon className={classes.loginIcon} />
{this.renderErrorMessage(title, description)}
</div>
);
}
renderLoginError({ type, title, description }) {
return type === 'error' && <SnackbarContent className={classes.loginError} message={this.renderMessage(title, description)} />;
}
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} />
{!username.value && !username.pristine && <FormHelperText id={'username-error'}>{username.helperText}</FormHelperText>}
</FormControl>
);
};
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} />
{!password.value && !password.pristine && <FormHelperText id={'password-error'}>{password.helperText}</FormHelperText>}
</FormControl>
);
};
renderActions = () => {
const {
form: { username, password },
} = this.state;
const { onCancel } = this.props;
return (
<DialogActions className={'dialog-footer'}>
<Button color={'inherit'} id={'login--form-cancel'} onClick={onCancel} type={'button'}>
{'Cancel'}
</Button>
<Button color={'inherit'} disabled={!password.value || !username.value} id={'login--form-submit'} type={'submit'}>
{'Login'}
</Button>
</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

@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LoginModal /> should load the component in default state 1`] = `"<div role=\\"dialog\\" class=\\"MuiModal-root-15 MuiDialog-root-1\\" id=\\"login--form-container\\"><div class=\\"MuiBackdrop-root-17\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiDialog-container-4 MuiDialog-scrollPaper-2\\" role=\\"document\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root-19 MuiPaper-elevation24-45 MuiPaper-rounded-20 MuiDialog-paper-5 MuiDialog-paperScrollPaper-6 MuiDialog-paperWidthXs-8 MuiDialog-paperFullWidth-13\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root-46\\"><h2 class=\\"MuiTypography-root-47 MuiTypography-title-53\\">Login</h2></div><div class=\\"MuiDialogContent-root-83\\"><div class=\\"MuiFormControl-root-84 MuiFormControl-fullWidth-87\\"><label class=\\"MuiFormLabel-root-99 MuiFormLabel-required-104 MuiInputLabel-required-92 MuiInputLabel-root-88 MuiInputLabel-formControl-93 MuiInputLabel-animated-96\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-105\\">*</span></label><div class=\\"MuiInputBase-root-119 MuiInput-root-106 MuiInput-underline-110 MuiInputBase-formControl-120 MuiInput-formControl-107\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-129 MuiInput-input-114\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-84 MuiFormControl-fullWidth-87\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-99 MuiFormLabel-required-104 MuiInputLabel-required-92 MuiInputLabel-root-88 MuiInputLabel-formControl-93 MuiInputLabel-animated-96\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-105\\">*</span></label><div class=\\"MuiInputBase-root-119 MuiInput-root-106 MuiInput-underline-110 MuiInputBase-formControl-120 MuiInput-formControl-107\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-129 MuiInput-input-114 MuiInputBase-inputType-132 MuiInput-inputType-117\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-136 dialog-footer\\"><button class=\\"MuiButtonBase-root-164 MuiButton-root-138 MuiButton-text-140 MuiButton-flat-143 MuiButton-colorInherit-159 MuiDialogActions-action-137\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-139\\">Cancel</span><span class=\\"MuiTouchRipple-root-167\\"></span></button><button class=\\"MuiButtonBase-root-164 MuiButtonBase-disabled-165 MuiButton-root-138 MuiButton-text-140 MuiButton-flat-143 MuiButton-disabled-158 MuiButton-colorInherit-159 MuiDialogActions-action-137\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-139\\">Login</span></button></div></form></div></div></div>"`;
exports[`<LoginModal /> should load the component with props 1`] = `"<div role=\\"dialog\\" class=\\"MuiModal-root-15 MuiDialog-root-1\\" id=\\"login--form-container\\"><div class=\\"MuiBackdrop-root-17\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiDialog-container-4 MuiDialog-scrollPaper-2\\" role=\\"document\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root-19 MuiPaper-elevation24-45 MuiPaper-rounded-20 MuiDialog-paper-5 MuiDialog-paperScrollPaper-6 MuiDialog-paperWidthXs-8 MuiDialog-paperFullWidth-13\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root-46\\"><h2 class=\\"MuiTypography-root-47 MuiTypography-title-53\\">Login</h2></div><div class=\\"MuiDialogContent-root-83\\"><div class=\\"MuiTypography-root-47 MuiTypography-body1-56 MuiPaper-root-19 MuiPaper-elevation6-27 MuiSnackbarContent-root-174 loginError\\" role=\\"alertdialog\\"><div class=\\"MuiSnackbarContent-message-175\\"><div class=\\"loginErrorMsg\\" id=\\"client-snackbar\\"><svg class=\\"MuiSvgIcon-root-177 loginIcon\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\\"></path></svg><span><div><strong>Error Title</strong></div><div>Error Description</div></span></div></div></div><div class=\\"MuiFormControl-root-84 MuiFormControl-fullWidth-87\\"><label class=\\"MuiFormLabel-root-99 MuiFormLabel-required-104 MuiInputLabel-required-92 MuiInputLabel-root-88 MuiInputLabel-formControl-93 MuiInputLabel-animated-96\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-105\\">*</span></label><div class=\\"MuiInputBase-root-119 MuiInput-root-106 MuiInput-underline-110 MuiInputBase-formControl-120 MuiInput-formControl-107\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-129 MuiInput-input-114\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-84 MuiFormControl-fullWidth-87\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-99 MuiFormLabel-required-104 MuiInputLabel-required-92 MuiInputLabel-root-88 MuiInputLabel-formControl-93 MuiInputLabel-animated-96\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-105\\">*</span></label><div class=\\"MuiInputBase-root-119 MuiInput-root-106 MuiInput-underline-110 MuiInputBase-formControl-120 MuiInput-formControl-107\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-129 MuiInput-input-114 MuiInputBase-inputType-132 MuiInput-inputType-117\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-136 dialog-footer\\"><button class=\\"MuiButtonBase-root-164 MuiButton-root-138 MuiButton-text-140 MuiButton-flat-143 MuiButton-colorInherit-159 MuiDialogActions-action-137\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-139\\">Cancel</span><span class=\\"MuiTouchRipple-root-167\\"></span></button><button class=\\"MuiButtonBase-root-164 MuiButtonBase-disabled-165 MuiButton-root-138 MuiButton-text-140 MuiButton-flat-143 MuiButton-disabled-158 MuiButton-colorInherit-159 MuiDialogActions-action-137\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-139\\">Login</span></button></div></form></div></div></div>"`;

View File

@@ -0,0 +1 @@
export { default } from './Login';

View File

@@ -0,0 +1,22 @@
@import '../../styles/variables';
.loginDialog {
min-width: 300px;
}
.loginError {
background-color: $red !important;
min-width: inherit !important;
margin-bottom: 10px !important;
}
.loginErrorMsg {
display: flex;
align-items: center;
}
.loginIcon {
opacity: 0.9;
margin-right: 8px;
}

Some files were not shown because too many files have changed in this diff Show More