Minor updates and fixes for campaign admin front-end

- Updated FormActionComponent prop from 'style' to 'bootstrapButtonStyle because 'style' is a built in React prop.

- Linted and added missing propTypes to campaign admin List component.

- Added missing initial state key to campaign reducer.

- Forced REST verb to lowercase in silverstripe-backend to make comparisions more robust.
This commit is contained in:
David Craig 2016-04-14 14:01:50 +12:00 committed by Damian Mooyman
parent d51e94c035
commit ef6a1f33ee
6 changed files with 64 additions and 57 deletions

View File

@ -24,7 +24,7 @@ The html id attribute.
Used for the button's `type` attribute. Defaults to `button` Used for the button's `type` attribute. Defaults to `button`
### style (string) ### bootstrapButtonStyle (string)
The style of button to be shown, adds a class `btn-{style}` to the button. Defaults to `secondary`. The style of button to be shown, adds a class `btn-{style}` to the button. Defaults to `secondary`.

View File

@ -37,7 +37,7 @@ class FormActionComponent extends SilverStripeComponent {
const buttonClasses = ['btn']; const buttonClasses = ['btn'];
// Add 'type' class // Add 'type' class
buttonClasses.push(`btn-${this.props.style}`); buttonClasses.push(`btn-${this.props.bootstrapButtonStyle}`);
// If there is no text // If there is no text
if (typeof this.props.label === 'undefined') { if (typeof this.props.label === 'undefined') {
@ -91,9 +91,13 @@ class FormActionComponent extends SilverStripeComponent {
* Event handler triggered when a user clicks the button. * Event handler triggered when a user clicks the button.
* *
* @param object event * @param object event
* @returns null * @return undefined
*/ */
handleClick(event) { handleClick(event) {
if (typeof this.props.handleClick === 'undefined') {
return;
}
this.props.handleClick(event); this.props.handleClick(event);
} }
@ -107,13 +111,13 @@ FormActionComponent.propTypes = {
loading: React.PropTypes.bool, loading: React.PropTypes.bool,
icon: React.PropTypes.string, icon: React.PropTypes.string,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
style: React.PropTypes.string, bootstrapButtonStyle: React.PropTypes.string,
extraClass: React.PropTypes.string, extraClass: React.PropTypes.string,
}; };
FormActionComponent.defaultProps = { FormActionComponent.defaultProps = {
type: 'button', type: 'button',
style: 'secondary', bootstrapButtonStyle: 'secondary',
disabled: false, disabled: false,
}; };

View File

@ -99,56 +99,53 @@ class CampaignListContainer extends SilverStripeComponent {
} }
renderButtonToolbar() { renderButtonToolbar() {
const items = this.getItems(this.props.campaignId); const items = this.getItems();
let itemSummaryLabel; // let itemSummaryLabel;
if (items) { if (!items) {
itemSummaryLabel = i18n.sprintf( return <div className="btn-toolbar"></div>;
(items.length === 1) ?
i18n._t('Campaigns.ITEM_SUMMARY_SINGULAR')
: i18n._t('Campaigns.ITEM_SUMMARY_PLURAL'),
items.length
);
let button;
if (this.props.record.State === 'open') {
button = (
<FormAction
label={i18n._t('Campaigns.PUBLISHCAMPAIGN')}
style={'success'}
loading={this.props.campaign.isPublishing}
handleClick={this.handlePublish}
icon={'rocket'}
/>
);
} else if (this.props.record.State === 'published') {
// TODO Implement "revert" feature
button = (
<FormAction
label={i18n._t('Campaigns.REVERTCAMPAIGN')}
style={'default'}
icon={'back-in-time'}
disabled
/>
);
}
// TODO Fix indicator positioning
// const itemCountIndicator = (
// <span className="text-muted">
// <span className="label label-warning label--empty">&nbsp;</span>
// &nbsp;{itemSummaryLabel}
// </span>
// );
return (
<div className="btn-toolbar">
{button}
</div>
);
} }
return <div className="btn-toolbar"></div>; // let itemSummaryLabel = i18n.sprintf(
// items.length === 1
// ? i18n._t('Campaigns.ITEM_SUMMARY_SINGULAR')
// : i18n._t('Campaigns.ITEM_SUMMARY_PLURAL'),
// items.length
// );
let actionProps = {};
if (this.props.record.State === 'open') {
actionProps = Object.assign(actionProps, {
label: i18n._t('Campaigns.PUBLISHCAMPAIGN'),
bootstrapButtonStyle: 'success',
loading: this.props.campaign.isPublishing,
handleClick: this.handlePublish,
icon: 'rocket',
});
} else if (this.props.record.State === 'published') {
// TODO Implement "revert" feature
actionProps = Object.assign(actionProps, {
label: i18n._t('Campaigns.REVERTCAMPAIGN'),
bootstrapButtonStyle: 'default',
icon: 'back-in-time',
disabled: true,
});
}
// TODO Fix indicator positioning
// const itemCountIndicator = (
// <span className="text-muted">
// <span className="label label-warning label--empty">&nbsp;</span>
// &nbsp;{itemSummaryLabel}
// </span>
// );
return (
<div className="btn-toolbar">
<FormAction {...actionProps} />
</div>
);
} }
/** /**
@ -219,8 +216,13 @@ class CampaignListContainer extends SilverStripeComponent {
} }
CampaignListContainer.propTypes = { CampaignListContainer.propTypes = {
campaign: React.PropTypes.shape({
isPublishing: React.PropTypes.bool.isRequired,
}),
campaignActions: React.PropTypes.object.isRequired,
publishApi: React.PropTypes.func.isRequired, publishApi: React.PropTypes.func.isRequired,
isPublishing: React.PropTypes.bool, record: React.PropTypes.object.isRequired,
recordActions: React.PropTypes.object.isRequired,
}; };
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
@ -230,7 +232,7 @@ function mapStateToProps(state, ownProps) {
record = state.records.ChangeSet[parseInt(ownProps.campaignId, 10)]; record = state.records.ChangeSet[parseInt(ownProps.campaignId, 10)];
} }
return { return {
record: record || [], record: record || {},
campaign: state.campaign, campaign: state.campaign,
}; };
} }

View File

@ -285,7 +285,7 @@ class SilverStripeBackend {
// Always add full payload data to GET requests. // Always add full payload data to GET requests.
// GET requests with a HTTP body are technically legal, // GET requests with a HTTP body are technically legal,
// but throw an error in the WHATWG fetch() implementation. // but throw an error in the WHATWG fetch() implementation.
{ setFromData: (refinedSpec.method === 'get') } { setFromData: (refinedSpec.method.toLowerCase() === 'get') }
); );
const encodedData = encode( const encodedData = encode(
@ -295,7 +295,7 @@ class SilverStripeBackend {
applySchemaToData(refinedSpec.payloadSchema, data) applySchemaToData(refinedSpec.payloadSchema, data)
); );
const args = refinedSpec.method === 'get' const args = refinedSpec.method.toLowerCase() === 'get'
? [url, headers] ? [url, headers]
: [url, encodedData, headers]; : [url, encodedData, headers];

View File

@ -3,6 +3,7 @@ import ACTION_TYPES from './action-types';
const initialState = { const initialState = {
campaignId: null, campaignId: null,
isPublishing: false,
view: null, view: null,
}; };

View File

@ -28,7 +28,7 @@ function recordsReducer(state = initialState, action) {
case ACTION_TYPES.FETCH_RECORDS_SUCCESS: case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
recordType = action.payload.recordType; recordType = action.payload.recordType;
// TODO Automatic pluralisation from recordType // TODO Automatic pluralisation from recordType
records = action.payload.data._embedded[`${recordType}s`] || []; records = action.payload.data._embedded[`${recordType}s`] || {};
records = records.reduce((prev, val) => Object.assign({}, prev, { [val.id]: val }), {}); records = records.reduce((prev, val) => Object.assign({}, prev, { [val.id]: val }), {});
return deepFreeze(Object.assign({}, state, { return deepFreeze(Object.assign({}, state, {
[recordType]: records, [recordType]: records,