Extension Review Guidelines - Frontend
Must haves
Testing
Only submit functional code. Remove all commented out code and do not submit code that is not executed.
To provide the best possible feedback on your integration, Shopgate focuses on the final functionality you want to deliver. Shopgate does not provide alpha or beta testing services. Remember to test your extension on both themes (GMD and iOS11), including edge cases (e.g. slow connection).
Imports
Do not import something directly from the Shopgate Themes. This approach may work during development, but it will break in production. Instead, use the components from our Engage package or via theme API.
Please contact Shopgate if you want to use any component from the theme that is not yet available via the theme API.
❌ Wrong
import ProductSlider from '../../../themes/theme-gmd/components/ProductSlider';
import ProductSlider from 'Components/ProductSlider';
✅ Right
import { useTheme } from '@shopgate/engage/core';
function MyComponent() {
const { ProductSlider } = useTheme();
return (
<div>
<ProductSlider />
</siv>
);
}
Use the Shopgate translation system
Shopgate provides a built-in translation system with some helpers. You should not hardcode any strings in your code. Put all text into a JSON file. For more information, see the adding custom translations tutorial.
❌ Wrong
MyComponent = () => (
<h1>My custom headline</h1>
)
✅ Right
import { I18n } from '@shopgate/engage/components';
MyComponent = () => (
<h1>
<I18n.Text string="myExt.headline" />
</h1>
)
Scope your translation string with a unique name
To avoid overriding any existing string from the Shopgate themes or other extensions, scope your strings in the local files. Shopgate recommends that you use the ID of your extension as the scope here.
If you want to explicitly override an existing string, you must not scope it.
❌ Wrong
// locale/en-US.json
{
"headline": "A nice headline"
}
✅ Right
// locale/en-US.json
{
"@myOrga/myExt": {
"headline": "A nice headline"
}
}
Declare dependencies to PWA
Add a peer dependency to your frontend/package.json to specify the minimum required PWA version for your extension.
✅ Right
"peerDependencies": {
"@shopgate/pwa-common": "^6.5.1",
"@shopgate/pwa-common-commerce": "^6.5.1",
"@shopgate/pwa-ui-shared/Price": "^6.5.1"
}
Colors and other variables
Use the Shopgate provided color and size variables rather than hard coding colors and sizes, where possible. This approach automatically configures your components using merchant colors and ensures that your components match the other App components.
❌ Wrong
const myPrimaryElement = css({
background: 'red',
});
✅ Right
import { themeConfig } from '@shopgate/pwa-common/helpers/config';
const myPrimaryElement = css({
background: themeConfig.colors.primary,
});
}
CSS selectors
Do not use Shopgate classNames or internal DOM structure to build CSS selectors.
Shopgate classNames are auto-generated, generic names that change with new PWA versions. The same rules apply to Shopgate component markup. Instead, add your own classNames and use them for styling. See our styling tutorial
❌ Wrong
<MyComponent>
<LibComponent />
</MyComponent>
.css-1p3dfz6 {
font-size: 20px;
}
.footer > div > ul {
display: none
}
✅ Right
<MyComponent>
<LibComponent className=”my-class” />
</MyComponent>
.my-class {
font-size: 20px;
}
Codestyle
Your code should be easy to read and follow some kind of consistent codestyle across your files. Shopgate recommends that you use our codestyle (which is based on airbnb) https://www.npmjs.com/package/@shopgate/eslint-config.
❌ Wrong
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import style from './style';
class Button extends Component {
get buttonProps() {
const {
children, testId, className, disabled, onClick, ...props
} = this.props; const buttonProps = {
className: `${className} ${style}`,
disabled,
onClick: disabled ? null : onClick,
...props,
};
return buttonProps;
}
render() {
return (<button data-test-id={this.props.testId} {...this.buttonProps}>{this.props.children}
</button>
);}}export default Button;
✅ Right
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import style from './style';
/**
* The button component.
*/
class Button extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
onClick: PropTypes.func,
testId: PropTypes.string,
};
static defaultProps = {
className: '',
disabled: false,
onClick: null,
testId: 'Button',
};
/**
* Getter for the calculated button props.
* @returns {Object}
*/
get buttonProps() {
const {
children, testId, className, disabled, onClick, ...props
} = this.props;
const buttonProps = {
className: `${className} ${style}`,
disabled,
onClick: disabled ? null : onClick,
...props,
};
return buttonProps;
}
/**
* Renders the component.
* @returns {JSX}
*/
render() {
return (
<button data-test-id={this.props.testId} {...this.buttonProps}>
{this.props.children}
</button>
);
}
}
export default Button;
Major
Error Handling
Use proper error handling. For example:
-
Never show hard errors to the user (e.g. some HTTPS status codes). Always provide a proper error message for these situations.
❌ Wrong "Internal Server error: can not read property check of undefined. someFile.js L12:13"
✅ Right "Sorry, something went wrong. Please try again"
-
Allow retries, if applicable.
-
Use proper error codes. You can find predefined error codes here: https://developer.shopgate.com/extension-review-guidelines/general-error-codes.
-
Ensure your UI still works and provides the user with feedback in case of an error.
Caching
If you send any requests to the backend, cache the response in the frontend in some way. This approach increases the performance of your app and reduces the load on the backend. See Fetching and processing data for more information.
Placeholders
If your UI displays something that needs to be fetched before, be sure to indicate this to the user and reserve some space for the content. Otherwise, the UI could jump as soon as the response comes in.
In addition to some placeholders, consider displaying a loading indicator.
import { LoadingProvider } from '@shopgate/pwa-common/providers';
LoadingProvider.setLoading("/your-current-page-pathname");
LoadingProvider.unsetLoading("/your-current-page-pathname");