Skip to content

Commit

Permalink
Merge pull request #5354 from Automattic/add/theme-sheet-selector
Browse files Browse the repository at this point in the history
Themes: Add site-selector modal to theme sheet
  • Loading branch information
seear committed May 19, 2016
2 parents 96b9cb2 + 7e5fef6 commit b5c6846
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 20 deletions.
34 changes: 30 additions & 4 deletions client/my-sites/theme/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import ActivatingTheme from 'components/data/activating-theme';
import ThanksModal from 'my-sites/themes/thanks-modal';
import QueryCurrentTheme from 'components/data/query-current-theme';
import { getCurrentTheme } from 'state/themes/current-theme/selectors';
import ThemesSiteSelectorModal from 'my-sites/themes/themes-site-selector-modal';
import actionLabels from 'my-sites/themes/action-labels';

const ThemeSheet = React.createClass( {
displayName: 'ThemeSheet',
Expand Down Expand Up @@ -59,6 +61,14 @@ const ThemeSheet = React.createClass( {
return { section: 'overview' };
},

getInitialState() {
return { selectedAction: null };
},

hideSiteSelectorModal() {
this.setState( { selectedAction: null } );
},

onBackClick() {
page.back();
},
Expand All @@ -73,12 +83,19 @@ const ThemeSheet = React.createClass( {
this.props.signup( this.props );
} else if ( this.isActive() ) {
this.props.customize( this.props, this.props.selectedSite );
// TODO: use site picker if no selected site
} else if ( isPremium( this.props ) ) {
// TODO: check theme is not already purchased
this.props.purchase( this.props, this.props.selectedSite, 'showcase-sheet' );
this.selectSiteAndDispatch( 'purchase' );
} else {
this.selectSiteAndDispatch( 'activate' );
}
},

selectSiteAndDispatch( action ) {
if ( this.props.selectedSite ) {
this.props[ action ]( this.props, this.props.selectedSite, 'showcase-sheet' );
} else {
this.props.activate( this.props, this.props.selectedSite, 'showcase-sheet' );
this.setState( { selectedAction: action } );
}
},

Expand Down Expand Up @@ -234,7 +251,7 @@ const ThemeSheet = React.createClass( {
}

const section = this.validateSection( this.props.section );
const priceElement = <span className="themes__sheet-action-bar-cost" dangerouslySetInnerHTML={ { __html: this.props.price } } />;
const priceElement = <span className="themes__sheet-action-bar-cost">{ this.props.price }</span>;
const siteID = this.props.selectedSite && this.props.selectedSite.ID;

return (
Expand All @@ -247,6 +264,15 @@ const ThemeSheet = React.createClass( {
source={ 'details' }
clearActivated={ this.props.clearActivated }/>
</ActivatingTheme>
{ this.state.selectedAction && <ThemesSiteSelectorModal
name={ this.state.selectedAction }
label={ actionLabels[ this.state.selectedAction ].label }
header={ actionLabels[ this.state.selectedAction ].header }
selectedTheme={ this.props }
onHide={ this.hideSiteSelectorModal }
action={ this.props[ this.state.selectedAction ] }
sourcePath={ `/theme/${ this.props.id }/${ section }` }
/> }
<div className="themes__sheet-columns">
<div className="themes__sheet-column-left">
<HeaderCake className="themes__sheet-action-bar" onClick={ this.onBackClick }>
Expand Down
2 changes: 2 additions & 0 deletions client/my-sites/themes/action-labels.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/** @ssr-ready **/

/**
* Internal dependencies
*/
Expand Down
1 change: 1 addition & 0 deletions client/my-sites/themes/multi-site.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const ThemesMultiSite = React.createClass( {
selectedTheme={ this.state.selectedTheme }
onHide={ this.hideSiteSelectorModal }
action={ bindActionCreators( Action[ this.state.selectedAction ], dispatch ) }
sourcePath={ '/design' }
/> }
</Main>
);
Expand Down
6 changes: 4 additions & 2 deletions client/my-sites/themes/themes-site-selector-modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const ThemesSiteSelectorModal = React.createClass( {
header: React.PropTypes.string.isRequired,
selectedTheme: PropTypes.object.isRequired,
onHide: PropTypes.func,
action: PropTypes.func
action: PropTypes.func,
// Will be prepended to site slug for a redirect on selection
sourcePath: PropTypes.string.isRequired,
},

redirectAndCallAction( site ) {
Expand All @@ -29,7 +31,7 @@ const ThemesSiteSelectorModal = React.createClass( {
*/
defer( () => {
trackClick( 'site selector', this.props.name );
page( '/design/' + site.slug );
page( this.props.sourcePath + '/' + site.slug );
this.props.action( this.props.selectedTheme, site );
} );
},
Expand Down
2 changes: 1 addition & 1 deletion client/state/themes/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function receiveThemeDetails( theme ) {
themeId: theme.id,
themeName: theme.name,
themeAuthor: theme.author,
themePrice: theme.price ? theme.price.display : undefined,
themePrice: theme.price,
themeScreenshot: theme.screenshot,
themeDescription: theme.description,
themeDescriptionLong: theme.description_long,
Expand Down
23 changes: 21 additions & 2 deletions client/state/themes/theme-details/selectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
/** @ssr-ready **/

export function getThemeDetails( state, id ) {
const theme = state.themes.themeDetails.get( id );
return theme ? theme.toJS() : {};
let theme = state.themes.themeDetails.get( id );
theme = theme ? theme.toJS() : {};
if ( theme.price ) {
theme.price = formatPrice( theme.price );
}
return theme;
}

// Convert price to format used by v1.2 themes API to fit with existing components.
// TODO (seear): remove when v1.2 theme details endpoint is added
function formatPrice( price ) {
if ( price.value === 0 ) {
// Free themes have plaintext string
return price.display;
}
// Premium themes have markup in price.display, so create our own plaintext
return price.value.toLocaleString( 'default', {
style: 'currency',
currency: price.currency,
minimumFractionDigits: 0,
} );
}
11 changes: 2 additions & 9 deletions server/isomorphic-routing/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* Internal dependencies
*/
import i18n from 'lib/mixins/i18n';
import { serverRender } from 'render';
import { createReduxStore } from 'state';
import { setSection as setSectionMiddlewareFactory } from '../../client/controller';
Expand All @@ -11,22 +10,21 @@ export function serverRouter( expressApp, setUpRoute, section ) {
expressApp.get(
route,
setUpRoute,
setUpI18n,
combineMiddlewares(
setSectionMiddlewareFactory( section ),
...middlewares
),
serverRender
);
}
};
}

function combineMiddlewares( ...middlewares ) {
return function( req, res, next ) {
req.context = getEnhancedContext( req );
applyMiddlewares( req.context, middlewares );
next();
}
};
}

// TODO: Maybe merge into getDefaultContext().
Expand All @@ -52,8 +50,3 @@ function compose( ...functions ) {
next => f( composed ) // eslint-disable-line no-unused-vars
), () => {} );
}

function setUpI18n( req, res, next ) {
i18n.initialize();
next();
}
10 changes: 9 additions & 1 deletion server/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ var config = require( 'config' ),
utils = require( 'bundler/utils' ),
sectionsModule = require( '../../client/sections' ),
serverRouter = require( 'isomorphic-routing' ).serverRouter,
serverRender = require( 'render' ).serverRender;
serverRender = require( 'render' ).serverRender,
i18n = require( 'lib/mixins/i18n' );

var HASH_LENGTH = 10,
URL_BASE_PATH = '/calypso',
Expand All @@ -37,6 +38,13 @@ sections.forEach( function( section ) {
} );
} );

// TODO: Move into an `app.get()` route handler (e.g. next to language setting),
// and pass the bootstrap locale as a parameter.
// However, this requires that i18n doesn't use global state to store the locale
// anymore (but e.g. a Redux subtree), or all users will get the same locale for
// SSR'd content that the first user had set after a deploy.
i18n.initialize();

/**
* Generates a hash of a files contents to be used as a version parameter on asset requests.
* @param {String} path Path to file we want to hash
Expand Down
1 change: 1 addition & 0 deletions server/pragma-checker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var IGNORED_MODULES = [
'lib/mixins/i18n', // ignore this until we make it work properly on the server
'lib/mixins/i18n/localize', // ignore this until we make it work properly on the server
'my-sites/themes/thanks-modal', // stubbed on the server until we develop an isomorphic version
'my-sites/themes/themes-site-selector-modal' // stubbed on the server until we develop an isomorphic version
];

function PragmaCheckPlugin( options ) {
Expand Down
3 changes: 2 additions & 1 deletion webpack.config.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ module.exports = {
new webpack.NormalModuleReplacementPlugin( /^lib\/analytics$/, 'lodash/noop' ), // Depends on BOM
new webpack.NormalModuleReplacementPlugin( /^lib\/upgrades\/actions$/, 'lodash/noop' ), // Uses Flux dispatcher
new webpack.NormalModuleReplacementPlugin( /^lib\/route$/, 'lodash/noop' ), // Depends too much on page.js
new webpack.NormalModuleReplacementPlugin( /^my-sites\/themes\/thanks-modal$/, 'components/empty-component' ) // Depends on BOM
new webpack.NormalModuleReplacementPlugin( /^my-sites\/themes\/thanks-modal$/, 'components/empty-component' ), // Depends on BOM
new webpack.NormalModuleReplacementPlugin( /^my-sites\/themes\/themes-site-selector-modal$/, 'components/empty-component' ) // Depends on BOM
],
externals: getExternals()
};

0 comments on commit b5c6846

Please sign in to comment.