Unverified Commit 0b5325f6 authored by Marcos Iglesias's avatar Marcos Iglesias Committed by GitHub

chore: Updating frontend dependencies (#751)

Signed-off-by: 's avatarMarcos Iglesias Valle <golodhros@gmail.com>
parent 49f0996c
...@@ -39,7 +39,7 @@ exports[`strict null compilation`] = { ...@@ -39,7 +39,7 @@ exports[`strict null compilation`] = {
"js/components/common/EditableSection/index.tsx:3911151831": [ "js/components/common/EditableSection/index.tsx:3911151831": [
[130, 12, 7, "Type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | null\' is not assignable to type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | undefined\'.\\n Type \'null\' is not assignable to type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | undefined\'.", "4055953994"] [130, 12, 7, "Type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | null\' is not assignable to type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | undefined\'.\\n Type \'null\' is not assignable to type \'((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | undefined\'.", "4055953994"]
], ],
"js/components/common/EditableText/index.tsx:3360221846": [ "js/components/common/EditableText/index.tsx:4277986546": [
[55, 4, 13, "Type \'null\' is not assignable to type \'(newValue: string, onSuccess?: (() => any) | undefined, onFailure?: (() => any) | undefined) => void\'.", "67794331"], [55, 4, 13, "Type \'null\' is not assignable to type \'(newValue: string, onSuccess?: (() => any) | undefined, onFailure?: (() => any) | undefined) => void\'.", "67794331"],
[56, 4, 14, "Type \'null\' is not assignable to type \'((onSuccess?: (() => any) | undefined, onFailure?: (() => any) | undefined) => void) | undefined\'.", "608289123"], [56, 4, 14, "Type \'null\' is not assignable to type \'((onSuccess?: (() => any) | undefined, onFailure?: (() => any) | undefined) => void) | undefined\'.", "608289123"],
[92, 4, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"], [92, 4, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"],
...@@ -56,17 +56,12 @@ exports[`strict null compilation`] = { ...@@ -56,17 +56,12 @@ exports[`strict null compilation`] = {
[21, 6, 9, "Type \'null\' is not assignable to type \'boolean | undefined\'.", "1517648899"], [21, 6, 9, "Type \'null\' is not assignable to type \'boolean | undefined\'.", "1517648899"],
[24, 6, 8, "Type \'null\' is not assignable to type \'boolean | undefined\'.", "2673870115"] [24, 6, 8, "Type \'null\' is not assignable to type \'boolean | undefined\'.", "2673870115"]
], ],
"js/components/common/OwnerEditor/index.tsx:3127875743": [ "js/components/common/OwnerEditor/index.tsx:487917110": [
[92, 4, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"], [119, 27, 6, "Type \'import(\\"./js/interfaces/Enums\\").UpdateMethod\' is not assignable to type \'never\'.", "1469012122"],
[97, 4, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"], [119, 56, 2, "Type \'string\' is not assignable to type \'never\'.", "5861160"],
[104, 27, 6, "Type \'UpdateMethod.DELETE\' is not assignable to type \'never\'.", "1469012122"], [124, 27, 6, "Type \'import(\\"./js/interfaces/Enums\\").UpdateMethod\' is not assignable to type \'never\'.", "1469012122"],
[104, 56, 2, "Type \'string\' is not assignable to type \'never\'.", "5861160"], [124, 53, 2, "Type \'string\' is not assignable to type \'never\'.", "5861160"],
[109, 27, 6, "Type \'UpdateMethod.PUT\' is not assignable to type \'never\'.", "1469012122"], [147, 12, 5, "Property \'value\' does not exist on type \'HTMLInputElement | null\'.", "189936718"]
[109, 53, 2, "Type \'string\' is not assignable to type \'never\'.", "5861160"],
[114, 6, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"],
[120, 6, 22, "Cannot invoke an object which is possibly \'undefined\'.", "2225424112"],
[127, 12, 5, "Property \'value\' does not exist on type \'HTMLInputElement | null\'.", "189936718"],
[129, 6, 21, "Object is possibly \'null\'.", "2411403565"]
], ],
"js/components/common/ResourceListItem/DashboardListItem/index.spec.tsx:53357032": [ "js/components/common/ResourceListItem/DashboardListItem/index.spec.tsx:53357032": [
[163, 14, 29, "Type \'null\' is not assignable to type \'number\'.", "1157138603"] [163, 14, 29, "Type \'null\' is not assignable to type \'number\'.", "1157138603"]
...@@ -100,9 +95,9 @@ exports[`strict null compilation`] = { ...@@ -100,9 +95,9 @@ exports[`strict null compilation`] = {
[63, 22, 6, "Type \'undefined\' is not assignable to type \'GetAllTagsRequest\'.", "1979467425"], [63, 22, 6, "Type \'undefined\' is not assignable to type \'GetAllTagsRequest\'.", "1979467425"],
[66, 4, 4, "Type \'undefined\' is not assignable to type \'Tag[]\'.", "2087952548"], [66, 4, 4, "Type \'undefined\' is not assignable to type \'Tag[]\'.", "2087952548"],
[67, 22, 6, "Type \'undefined\' is not assignable to type \'UpdateTagsRequest\'.", "1979467425"], [67, 22, 6, "Type \'undefined\' is not assignable to type \'UpdateTagsRequest\'.", "1979467425"],
[100, 31, 10, "Type \'UpdateMethod.DELETE\' is not assignable to type \'never\'.", "3392756765"], [100, 31, 10, "Type \'import(\\"./js/interfaces/Enums\\").UpdateMethod\' is not assignable to type \'never\'.", "3392756765"],
[100, 64, 7, "Type \'string\' is not assignable to type \'never\'.", "1671693616"], [100, 64, 7, "Type \'string\' is not assignable to type \'never\'.", "1671693616"],
[102, 31, 10, "Type \'UpdateMethod.PUT\' is not assignable to type \'never\'.", "3392756765"], [102, 31, 10, "Type \'import(\\"./js/interfaces/Enums\\").UpdateMethod\' is not assignable to type \'never\'.", "3392756765"],
[102, 61, 7, "Type \'string\' is not assignable to type \'never\'.", "1671693616"] [102, 61, 7, "Type \'string\' is not assignable to type \'never\'.", "1671693616"]
], ],
"js/components/common/Tags/TagsList/index.tsx:4084208491": [ "js/components/common/Tags/TagsList/index.tsx:4084208491": [
...@@ -145,8 +140,6 @@ exports[`strict null compilation`] = { ...@@ -145,8 +140,6 @@ exports[`strict null compilation`] = {
], ],
"js/ducks/dashboard/reducer.ts:562296055": [ "js/ducks/dashboard/reducer.ts:562296055": [
[49, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "2818833716"], [49, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "2818833716"],
[55, 2, 18, "Type \'null\' is not assignable to type \'number\'.", "2901767208"],
[56, 2, 29, "Type \'null\' is not assignable to type \'number\'.", "1157138603"],
[61, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "3048879712"], [61, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "3048879712"],
[64, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "1885086497"], [64, 2, 17, "Type \'null\' is not assignable to type \'number\'.", "1885086497"],
[71, 2, 10, "Type \'null\' is not assignable to type \'number\'.", "3382497788"], [71, 2, 10, "Type \'null\' is not assignable to type \'number\'.", "3382497788"],
...@@ -253,10 +246,8 @@ exports[`strict null compilation`] = { ...@@ -253,10 +246,8 @@ exports[`strict null compilation`] = {
"js/pages/DashboardPage/DashboardOwnerEditor/index.spec.tsx:4125678807": [ "js/pages/DashboardPage/DashboardOwnerEditor/index.spec.tsx:4125678807": [
[29, 19, 11, "Type \'{ manager_id: null; manager_fullname: null; manager_email: null; profile_url: string; role_name: null; display_name: null; github_username: null; team_name: null; last_name: null; full_name: null; ... 6 more ...; user_id: string; }\' is not assignable to type \'User\'.\\n Types of property \'display_name\' are incompatible.\\n Type \'null\' is not assignable to type \'string\'.", "2041815528"] [29, 19, 11, "Type \'{ manager_id: null; manager_fullname: null; manager_email: null; profile_url: string; role_name: null; display_name: null; github_username: null; team_name: null; last_name: null; full_name: null; ... 6 more ...; user_id: string; }\' is not assignable to type \'User\'.\\n Types of property \'display_name\' are incompatible.\\n Type \'null\' is not assignable to type \'string\'.", "2041815528"]
], ],
"js/pages/DashboardPage/index.spec.tsx:3245519479": [ "js/pages/DashboardPage/index.spec.tsx:3811826221": [
[53, 4, 8, "Argument of type \'Partial<Location<{} | null | undefined>> | undefined\' is not assignable to parameter of type \'Partial<Location<{} | null | undefined>>\'.\\n Type \'undefined\' is not assignable to type \'Partial<Location<{} | null | undefined>>\'.", "2700611480"], [53, 4, 8, "Argument of type \'Partial<Location<{} | null | undefined>> | undefined\' is not assignable to parameter of type \'Partial<Location<{} | null | undefined>>\'.\\n Type \'undefined\' is not assignable to type \'Partial<Location<{} | null | undefined>>\'.", "2700611480"]
[213, 10, 18, "Type \'null\' is not assignable to type \'number\'.", "2901767208"],
[214, 10, 29, "Type \'null\' is not assignable to type \'number\'.", "1157138603"]
], ],
"js/pages/ProfilePage/index.spec.tsx:4086464143": [ "js/pages/ProfilePage/index.spec.tsx:4086464143": [
[41, 6, 4, "Argument of type \'null\' is not assignable to parameter of type \'Partial<Location<{} | null | undefined>>\'.", "2087897566"] [41, 6, 4, "Argument of type \'null\' is not assignable to parameter of type \'Partial<Location<{} | null | undefined>>\'.", "2087897566"]
......
...@@ -105,7 +105,7 @@ describe('EditableText', () => { ...@@ -105,7 +105,7 @@ describe('EditableText', () => {
describe('render', () => { describe('render', () => {
describe('not in edit mode', () => { describe('not in edit mode', () => {
const { props, wrapper } = setup({ const { wrapper } = setup({
isEditing: false, isEditing: false,
value: '', value: '',
}); });
...@@ -114,9 +114,6 @@ describe('EditableText', () => { ...@@ -114,9 +114,6 @@ describe('EditableText', () => {
it('renders a ReactMarkdown component', () => { it('renders a ReactMarkdown component', () => {
const markdown = wrapper.find(ReactMarkdown); const markdown = wrapper.find(ReactMarkdown);
expect(markdown.exists()).toBe(true); expect(markdown.exists()).toBe(true);
expect(markdown.props()).toMatchObject({
source: wrapper.state().value,
});
}); });
it('renders an edit link if it is editable and the text is empty', () => { it('renders an edit link if it is editable and the text is empty', () => {
......
...@@ -120,13 +120,16 @@ class EditableText extends React.Component< ...@@ -120,13 +120,16 @@ class EditableText extends React.Component<
}; };
render() { render() {
if (!this.props.isEditing) { const { isEditing, editable, maxLength } = this.props;
const { value = '', isDisabled } = this.state;
if (!isEditing) {
return ( return (
<div className="editable-text"> <div className="editable-text">
<div className="markdown-wrapper"> <div className="markdown-wrapper">
<ReactMarkdown source={this.state.value} escapeHtml={false} /> <ReactMarkdown allowDangerousHtml={false}>{value}</ReactMarkdown>
</div> </div>
{this.props.editable && !this.state.value && ( {editable && !value && (
<a <a
className="edit-link" className="edit-link"
href="JavaScript:void(0)" href="JavaScript:void(0)"
...@@ -144,13 +147,13 @@ class EditableText extends React.Component< ...@@ -144,13 +147,13 @@ class EditableText extends React.Component<
<textarea <textarea
className="editable-textarea" className="editable-textarea"
rows={2} rows={2}
maxLength={this.props.maxLength} maxLength={maxLength}
ref={this.textAreaRef} ref={this.textAreaRef}
defaultValue={this.state.value} defaultValue={value}
disabled={this.state.isDisabled} disabled={isDisabled}
/> />
<div className="editable-textarea-controls"> <div className="editable-textarea-controls">
{this.state.isDisabled && ( {isDisabled && (
<> <>
<h2 className="label label-danger refresh-message"> <h2 className="label label-danger refresh-message">
{REFRESH_MESSAGE} {REFRESH_MESSAGE}
...@@ -158,16 +161,18 @@ class EditableText extends React.Component< ...@@ -158,16 +161,18 @@ class EditableText extends React.Component<
<button <button
className="btn btn-primary refresh-button" className="btn btn-primary refresh-button"
onClick={this.refreshText} onClick={this.refreshText}
type="button"
> >
<img className="icon icon-refresh" alt="" /> <img className="icon icon-refresh" alt="" />
{REFRESH_BUTTON_TEXT} {REFRESH_BUTTON_TEXT}
</button> </button>
</> </>
)} )}
{!this.state.isDisabled && ( {!isDisabled && (
<button <button
className="btn btn-primary update-button" className="btn btn-primary update-button"
onClick={this.updateText} onClick={this.updateText}
type="button"
> >
{UPDATE_BUTTON_TEXT} {UPDATE_BUTTON_TEXT}
</button> </button>
...@@ -175,6 +180,7 @@ class EditableText extends React.Component< ...@@ -175,6 +180,7 @@ class EditableText extends React.Component<
<button <button
className="btn btn-default cancel-button" className="btn btn-default cancel-button"
onClick={this.exitEditMode} onClick={this.exitEditMode}
type="button"
> >
{CANCEL_BUTTON_TEXT} {CANCEL_BUTTON_TEXT}
</button> </button>
......
...@@ -80,54 +80,76 @@ export class OwnerEditor extends React.Component< ...@@ -80,54 +80,76 @@ export class OwnerEditor extends React.Component<
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { itemProps } = this.props;
// TODO - itemProps is a new object and this check needs to be fixed // TODO - itemProps is a new object and this check needs to be fixed
if (prevProps.itemProps !== this.props.itemProps) { if (prevProps.itemProps !== itemProps) {
this.setState({ this.setState({
itemProps: this.props.itemProps, itemProps,
tempItemProps: this.props.itemProps, tempItemProps: itemProps,
}); });
} }
} }
handleShow = () => { handleShow = () => {
this.props.setEditMode(true); const { setEditMode } = this.props;
if (setEditMode) {
setEditMode(true);
}
}; };
cancelEdit = () => { cancelEdit = () => {
this.setState({ tempItemProps: this.state.itemProps }); const { setEditMode } = this.props;
this.props.setEditMode(false); const { itemProps } = this.state;
this.setState({ tempItemProps: itemProps });
if (setEditMode) {
setEditMode(false);
}
}; };
saveEdit = () => { saveEdit = () => {
const { itemProps, tempItemProps } = this.state;
const { setEditMode, onUpdateList } = this.props;
const updateArray = []; const updateArray = [];
Object.keys(this.state.itemProps).forEach((key) => {
if (!this.state.tempItemProps.hasOwnProperty(key)) { Object.keys(itemProps).forEach((key) => {
if (!tempItemProps.hasOwnProperty(key)) {
updateArray.push({ method: UpdateMethod.DELETE, id: key }); updateArray.push({ method: UpdateMethod.DELETE, id: key });
} }
}); });
Object.keys(this.state.tempItemProps).forEach((key) => { Object.keys(tempItemProps).forEach((key) => {
if (!this.state.itemProps.hasOwnProperty(key)) { if (!itemProps.hasOwnProperty(key)) {
updateArray.push({ method: UpdateMethod.PUT, id: key }); updateArray.push({ method: UpdateMethod.PUT, id: key });
} }
}); });
const onSuccessCallback = () => { const onSuccessCallback = () => {
this.props.setEditMode(false); if (setEditMode) {
setEditMode(false);
}
}; };
const onFailureCallback = () => { const onFailureCallback = () => {
this.setState({ this.setState({
errorText: Constants.DEFAULT_ERROR_TEXT, errorText: Constants.DEFAULT_ERROR_TEXT,
}); });
this.props.setEditMode(false); if (setEditMode) {
setEditMode(false);
}
}; };
this.props.onUpdateList(updateArray, onSuccessCallback, onFailureCallback);
onUpdateList(updateArray, onSuccessCallback, onFailureCallback);
}; };
recordAddItem = (event: React.FormEvent<HTMLFormElement>) => { recordAddItem = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
const { value } = this.inputRef.current; const { value } = this.inputRef.current;
if (value) {
if (this.inputRef.current && value) {
this.inputRef.current.value = ''; this.inputRef.current.value = '';
const newTempItemProps = { const newTempItemProps = {
...this.state.tempItemProps, ...this.state.tempItemProps,
[value]: { label: value }, [value]: { label: value },
...@@ -137,23 +159,27 @@ export class OwnerEditor extends React.Component< ...@@ -137,23 +159,27 @@ export class OwnerEditor extends React.Component<
}; };
recordDeleteItem = (deletedKey: string) => { recordDeleteItem = (deletedKey: string) => {
const newTempItemProps = Object.keys(this.state.tempItemProps) const { tempItemProps } = this.state;
const newTempItemProps = Object.keys(tempItemProps)
.filter((key) => { .filter((key) => {
return key !== deletedKey; return key !== deletedKey;
}) })
.reduce((obj, key) => { .reduce((obj, key) => {
obj[key] = this.state.tempItemProps[key]; obj[key] = tempItemProps[key];
return obj; return obj;
}, {}); }, {});
this.setState({ tempItemProps: newTempItemProps }); this.setState({ tempItemProps: newTempItemProps });
}; };
renderModalBody = () => { renderModalBody = () => {
if (!this.props.isEditing) { const { isEditing, isLoading } = this.props;
if (!isEditing) {
return null; return null;
} }
if (this.props.isLoading) { if (isLoading) {
return ( return (
<Modal.Body> <Modal.Body>
<LoadingSpinner /> <LoadingSpinner />
...@@ -191,6 +217,7 @@ export class OwnerEditor extends React.Component< ...@@ -191,6 +217,7 @@ export class OwnerEditor extends React.Component<
/* tslint:disable - TODO: Investigate jsx-no-lambda rule */ /* tslint:disable - TODO: Investigate jsx-no-lambda rule */
onClick={() => this.recordDeleteItem(key)} onClick={() => this.recordDeleteItem(key)}
/* tslint:enable */ /* tslint:enable */
type="button"
> >
<span className="sr-only">{Constants.DELETE_ITEM}</span> <span className="sr-only">{Constants.DELETE_ITEM}</span>
<img className="icon icon-delete" alt="" /> <img className="icon icon-delete" alt="" />
...@@ -205,20 +232,21 @@ export class OwnerEditor extends React.Component< ...@@ -205,20 +232,21 @@ export class OwnerEditor extends React.Component<
render() { render() {
const { isEditing, readOnly, resourceType } = this.props; const { isEditing, readOnly, resourceType } = this.props;
const hasItems = Object.keys(this.state.itemProps).length > 0; const { errorText, itemProps } = this.state;
const hasItems = Object.keys(itemProps).length > 0;
if (this.state.errorText) { if (errorText) {
return ( return (
<div className="owner-editor-component"> <div className="owner-editor-component">
<label className="status-message">{this.state.errorText}</label> <label className="status-message">{errorText}</label>
</div> </div>
); );
} }
const ownerList = hasItems ? ( const ownerList = hasItems ? (
<ul className="component-list"> <ul className="component-list">
{Object.keys(this.state.itemProps).map((key) => { {Object.keys(itemProps).map((key) => {
const owner = this.state.itemProps[key]; const owner = itemProps[key];
const avatarLabel = React.createElement(AvatarLabel, owner); const avatarLabel = React.createElement(AvatarLabel, owner);
let listItem; let listItem;
......
...@@ -14,8 +14,8 @@ export interface DashboardMetadata { ...@@ -14,8 +14,8 @@ export interface DashboardMetadata {
group_name: string; group_name: string;
group_url: string; group_url: string;
last_run_state: string; last_run_state: string;
last_run_timestamp: number; last_run_timestamp: number | null;
last_successful_run_timestamp: number; last_successful_run_timestamp: number | null;
name: string; name: string;
owners: User[]; owners: User[];
product: string; product: string;
......
...@@ -168,9 +168,6 @@ describe('DashboardPage', () => { ...@@ -168,9 +168,6 @@ describe('DashboardPage', () => {
it('using a ReactMarkdown component', () => { it('using a ReactMarkdown component', () => {
const markdown = wrapper.find(ReactMarkdown); const markdown = wrapper.find(ReactMarkdown);
expect(markdown.exists()).toBe(true); expect(markdown.exists()).toBe(true);
expect(markdown.props()).toMatchObject({
source: props.dashboard.description,
});
}); });
it('with link to add description if none exists', () => { it('with link to add description if none exists', () => {
......
...@@ -240,7 +240,7 @@ export class DashboardPage extends React.Component< ...@@ -240,7 +240,7 @@ export class DashboardPage extends React.Component<
> >
{hasDescription && ( {hasDescription && (
<div className="markdown-wrapper"> <div className="markdown-wrapper">
<ReactMarkdown source={dashboard.description} /> <ReactMarkdown>{dashboard.description}</ReactMarkdown>
</div> </div>
)} )}
{!hasDescription && ( {!hasDescription && (
......
...@@ -19,7 +19,7 @@ interface StringDateConfig { ...@@ -19,7 +19,7 @@ interface StringDateConfig {
type DateConfig = TimestampDateConfig | EpochDateConfig | StringDateConfig; type DateConfig = TimestampDateConfig | EpochDateConfig | StringDateConfig;
// This function is only exported for testing // This function is only exported for testing
export function getMomentDate(config: DateConfig): Moment { export function getMomentDate(config: DateConfig): Moment.Moment {
let moment; let moment;
const { timestamp } = config as TimestampDateConfig; const { timestamp } = config as TimestampDateConfig;
const epoch = (config as EpochDateConfig).epochTimestamp; const epoch = (config as EpochDateConfig).epochTimestamp;
......
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"tsconfig-paths": "^3.9.0", "tsconfig-paths": "^3.9.0",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"typescript": "^3.9.2", "typescript": "^4.0.5",
"webpack": "^4.44.1", "webpack": "^4.44.1",
"webpack-bundle-analyzer": "^3.8.0", "webpack-bundle-analyzer": "^3.8.0",
"webpack-cli": "^3.3.11", "webpack-cli": "^3.3.11",
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"form-serialize": "^0.7.2", "form-serialize": "^0.7.2",
"jquery": "^3.5.0", "jquery": "^3.5.0",
"moment-timezone": "^0.5.28", "moment-timezone": "^0.5.31",
"react": "^16.13.1", "react": "^16.13.1",
"react-avatar": "^2.5.1", "react-avatar": "^2.5.1",
"react-bootstrap": "^0.32.4", "react-bootstrap": "^0.32.4",
...@@ -142,7 +142,7 @@ ...@@ -142,7 +142,7 @@
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-js-pagination": "^3.0.3", "react-js-pagination": "^3.0.3",
"react-linkify": "^0.2.2", "react-linkify": "^0.2.2",
"react-markdown": "^4.3.1", "react-markdown": "^5.0.2",
"react-redux": "^5.1.0", "react-redux": "^5.1.0",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-sanitized-html": "^2.0.0", "react-sanitized-html": "^2.0.0",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment