Skip to content

Commit

Permalink
chore: Use redux to manage UI state (ref #1250)
Browse files Browse the repository at this point in the history
  • Loading branch information
tofumatt committed Sep 23, 2017
1 parent f091728 commit 79f859c
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 74 deletions.
52 changes: 32 additions & 20 deletions src/amo/components/ReportAbuseButton/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import classNames from 'classnames';
import { oneLine } from 'common-tags';
import defaultDebounce from 'lodash.debounce';
import React from 'react';
import { connect } from 'react-redux';
Expand All @@ -7,7 +8,14 @@ import { compose } from 'redux';

import { withErrorHandler } from 'core/errorHandler';
import translate from 'core/i18n/translate';
import { sendAddonAbuseReport } from 'core/reducers/abuse';
import log from 'core/logger';
import {
disableAbuseButtonUI,
enableAbuseButtonUI,
hideAddonAbuseReportUI,
sendAddonAbuseReport,
showAddonAbuseReportUI,
} from 'core/reducers/abuse';
import { sanitizeHTML } from 'core/utils';
import Button from 'ui/components/Button';

Expand All @@ -29,20 +37,16 @@ export class ReportAbuseButtonBase extends React.Component {
debounce: defaultDebounce,
};

constructor(props: Object) {
super(props);

this.state = { buttonEnabled: false, expanded: false };
}

cancelReport = (event) => {
event.preventDefault();

if (this.props.loading) {
const { addon, dispatch, loading } = this.props;

if (loading) {
return;
}

this.setState({ expanded: false });
dispatch(hideAddonAbuseReportUI({ addon }));
}

sendReport = (event) => {
Expand All @@ -51,6 +55,9 @@ export class ReportAbuseButtonBase extends React.Component {
// The button isn't clickable if there is no content, but because we
// debounce the checks we just verify there's a message to send.
if (!this.textarea.value.length) {
log.debug(oneLine`User managed to click submit button before
disableAbuseButtonUI() was called because of debouncing textarea
onChange event. Ignoring this onClick event.`);
return;
}

Expand All @@ -66,16 +73,20 @@ export class ReportAbuseButtonBase extends React.Component {
showMore = (event) => {
event.preventDefault();

this.setState({ expanded: true }, function focusTextarea() {
this.textarea.focus();
});
const { addon, dispatch } = this.props;

dispatch(showAddonAbuseReportUI({ addon }));
this.textarea.focus();
}

textareaChange = this.props.debounce(() => {
if (this.textarea.value.length) {
this.setState({ buttonEnabled: true });
} else {
this.setState({ buttonEnabled: false });
const { abuseReport, addon, dispatch } = this.props;

// Don't dispatch the UI update if the button is already visible.
if (this.textarea.value.length && !abuseReport.buttonEnabled) {
dispatch(enableAbuseButtonUI({ addon }));
} else if (!this.textarea.value.length) {
dispatch(disableAbuseButtonUI({ addon }));
}
}, 100, { trailing: true })

Expand All @@ -88,7 +99,7 @@ export class ReportAbuseButtonBase extends React.Component {
return null;
}

if (abuseReport) {
if (abuseReport && abuseReport.message) {
return (
<div className="ReportAbuseButton ReportAbuseButton--report-sent">
<h3 className="ReportAbuseButton-header">
Expand All @@ -112,7 +123,7 @@ export class ReportAbuseButtonBase extends React.Component {
);
}

const sendButtonIsDisabled = loading || !this.state.buttonEnabled;
const sendButtonIsDisabled = loading || !abuseReport.buttonEnabled;

const prefaceText = i18n.sprintf(i18n.gettext(
`If you think this add-on violates
Expand All @@ -128,7 +139,7 @@ export class ReportAbuseButtonBase extends React.Component {
return (
<div
className={classNames('ReportAbuseButton', {
'ReportAbuseButton--is-expanded': this.state.expanded,
'ReportAbuseButton--is-expanded': abuseReport.uiVisible,
})}
>
<div className="ReportAbuseButton--preview">
Expand Down Expand Up @@ -196,7 +207,8 @@ export const mapStateToProps = (state, ownProps) => {
const addon = ownProps.addon;

return {
abuseReport: addon ? state.abuse.bySlug[addon.slug] : null,
abuseReport: addon && state.abuse.bySlug[addon.slug] ?
state.abuse.bySlug[addon.slug] : {},
loading: state.abuse.loading,
};
};
Expand Down
127 changes: 121 additions & 6 deletions src/core/reducers/abuse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,57 @@
/* @flow */
import type { AddonType } from 'core/types/addons';

export const DISABLE_ADDON_ABUSE_BUTTON_UI = 'DISABLE_ADDON_ABUSE_BUTTON_UI';
export const ENABLE_ADDON_ABUSE_BUTTON_UI = 'ENABLE_ADDON_ABUSE_BUTTON_UI';
export const HIDE_ADDON_ABUSE_REPORT_UI = 'HIDE_ADDON_ABUSE_REPORT_UI';
export const LOAD_ADDON_ABUSE_REPORT = 'LOAD_ADDON_ABUSE_REPORT';
export const SEND_ADDON_ABUSE_REPORT = 'SEND_ADDON_ABUSE_REPORT';
export const SHOW_ADDON_ABUSE_REPORT_UI = 'SHOW_ADDON_ABUSE_REPORT_UI';

type DisableAddonAbuseButtonUIType = { addon: AddonType };

export function disableAbuseButtonUI(
{ addon }: DisableAddonAbuseButtonUIType = {}
) {
if (!addon) {
throw new Error('addon is required');
}

return {
type: DISABLE_ADDON_ABUSE_BUTTON_UI,
payload: { addon },
};
}

type EnableAddonAbuseButtonUIType = { addon: AddonType };

export function enableAbuseButtonUI(
{ addon }: EnableAddonAbuseButtonUIType = {}
) {
if (!addon) {
throw new Error('addon is required');
}

return {
type: ENABLE_ADDON_ABUSE_BUTTON_UI,
payload: { addon },
};
}

type HideAddonAbuseReportUIType = { addon: AddonType };

export function hideAddonAbuseReportUI(
{ addon }: HideAddonAbuseReportUIType = {}
) {
if (!addon) {
throw new Error('addon is required');
}

return {
type: HIDE_ADDON_ABUSE_REPORT_UI,
payload: { addon },
};
}

type LoadAddonAbuseReportType = {
addon: {|
Expand All @@ -13,7 +64,7 @@ type LoadAddonAbuseReportType = {
};

export function loadAddonAbuseReport(
{ addon, message, reporter }: LoadAddonAbuseReportType
{ addon, message, reporter }: LoadAddonAbuseReportType = {}
) {
if (!addon) {
throw new Error('addon is required');
Expand All @@ -38,7 +89,7 @@ type SendAddonAbuseReportAction = {|
|};

export function sendAddonAbuseReport(
{ addonSlug, errorHandlerId, message }: SendAddonAbuseReportAction
{ addonSlug, errorHandlerId, message }: SendAddonAbuseReportAction = {}
) {
if (!addonSlug) {
throw new Error('addonSlug is required');
Expand All @@ -56,14 +107,34 @@ export function sendAddonAbuseReport(
};
}

type ShowAddonAbuseReportUIType = { addon: AddonType };

export function showAddonAbuseReportUI(
{ addon }: ShowAddonAbuseReportUIType = {}
) {
if (!addon) {
throw new Error('addon is required');
}

return {
type: SHOW_ADDON_ABUSE_REPORT_UI,
payload: { addon },
};
}

export const initialState = {
bySlug: {},
loading: false,
};

type ReducerState = {|
bySlug: {
[addonSlug: string]: {| message: string, reporter: Object | null |},
[addonSlug: string]: {|
buttonEnabled?: bool,
message: string,
reporter: Object | null,
uiVisible?: bool,
|},
},
loading: bool,
|};
Expand All @@ -73,19 +144,63 @@ export default function abuseReducer(
action: Object
) {
switch (action.type) {
case SEND_ADDON_ABUSE_REPORT:
return { ...state, loading: true };
case DISABLE_ADDON_ABUSE_BUTTON_UI: {
const { addon } = action.payload;

return {
...state,
bySlug: {
...state.bySlug,
[addon.slug]: { ...state.bySlug[addon.slug], buttonEnabled: false },
},
};
}
case ENABLE_ADDON_ABUSE_BUTTON_UI: {
const { addon } = action.payload;

return {
...state,
bySlug: {
...state.bySlug,
[addon.slug]: { ...state.bySlug[addon.slug], buttonEnabled: true },
},
};
}
case HIDE_ADDON_ABUSE_REPORT_UI: {
const { addon } = action.payload;

return {
...state,
bySlug: {
...state.bySlug,
[addon.slug]: { ...state.bySlug[addon.slug], uiVisible: false },
},
};
}
case LOAD_ADDON_ABUSE_REPORT: {
const { addon, message, reporter } = action.payload;
return {
...state,
bySlug: {
...state.bySlug,
[addon.slug]: { message, reporter },
[addon.slug]: { message, reporter, uiVisible: false },
},
loading: false,
};
}
case SEND_ADDON_ABUSE_REPORT:
return { ...state, loading: true };
case SHOW_ADDON_ABUSE_REPORT_UI: {
const { addon } = action.payload;

return {
...state,
bySlug: {
...state.bySlug,
[addon.slug]: { ...state.bySlug[addon.slug], uiVisible: true },
},
};
}
default:
return state;
}
Expand Down

0 comments on commit 79f859c

Please sign in to comment.