diff --git a/client/analytics/report/customers/config.js b/client/analytics/report/customers/config.js
index 5ee8e7b1bcb..ab1cac2b101 100644
--- a/client/analytics/report/customers/config.js
+++ b/client/analytics/report/customers/config.js
@@ -251,6 +251,36 @@ export const advancedFilters = {
component: 'Currency',
},
},
+ registered: {
+ labels: {
+ add: __( 'Registered', 'wc-admin' ),
+ remove: __( 'Remove registered filter', 'wc-admin' ),
+ rule: __( 'Select a registered filter match', 'wc-admin' ),
+ /* translators: A sentence describing a Product filter. See screen shot for context: https://cloudup.com/cCsm3GeXJbE */
+ title: __( 'Registered {{rule /}} {{filter /}}', 'wc-admin' ),
+ filter: __( 'Select registered date', 'wc-admin' ),
+ },
+ rules: [
+ {
+ value: 'before',
+ /* translators: Sentence fragment, logical, "Before" refers to customers registered before a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
+ label: _x( 'Before', 'date', 'wc-admin' ),
+ },
+ {
+ value: 'after',
+ /* translators: Sentence fragment, logical, "after" refers to customers registered after a given date. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
+ label: _x( 'After', 'date', 'wc-admin' ),
+ },
+ {
+ value: 'between',
+ /* translators: Sentence fragment, logical, "Between" refers to average order value of a customer, between two given amounts. Screenshot for context: https://cloudup.com/cCsm3GeXJbE */
+ label: _x( 'Between', 'date', 'wc-admin' ),
+ },
+ ],
+ input: {
+ component: 'Date',
+ },
+ },
},
};
/*eslint-enable max-len*/
diff --git a/packages/components/src/filters/advanced/date-filter.js b/packages/components/src/filters/advanced/date-filter.js
new file mode 100644
index 00000000000..fba117a8e7a
--- /dev/null
+++ b/packages/components/src/filters/advanced/date-filter.js
@@ -0,0 +1,199 @@
+/** @format */
+/**
+ * External dependencies
+ */
+import { Component } from '@wordpress/element';
+import interpolateComponents from 'interpolate-components';
+import { SelectControl } from '@wordpress/components';
+import { find, partial } from 'lodash';
+import classnames from 'classnames';
+import { __, _x } from '@wordpress/i18n';
+import moment from 'moment';
+
+/**
+ * WooCommerce dependencies
+ */
+import { isoDateFormat, toMoment } from '@woocommerce/date';
+
+/**
+ * Internal dependencies
+ */
+import DatePicker from '../../calendar/date-picker';
+
+const dateStringFormat = __( 'MMM D, YYYY', 'wc-admin' );
+const dateFormat = __( 'MM/DD/YYYY', 'wc-admin' );
+
+class DateFilter extends Component {
+ constructor( { filter } ) {
+ super( ...arguments );
+
+ const [ isoAfter, isoBefore ] = ( filter.value || '' ).split( ',' );
+ const after = isoAfter ? toMoment( isoDateFormat, isoAfter ) : null;
+ const before = isoBefore ? toMoment( isoDateFormat, isoBefore ) : null;
+
+ this.state = {
+ before,
+ beforeText: before ? before.format( dateFormat ) : '',
+ beforeError: null,
+ after,
+ afterText: after ? after.format( dateFormat ) : '',
+ afterError: null,
+ };
+
+ this.onSingleDateChange = this.onSingleDateChange.bind( this );
+ this.onRangeDateChange = this.onRangeDateChange.bind( this );
+ }
+
+ getBetweenString() {
+ return _x(
+ '{{after /}}{{span}}and{{/span}}{{before /}}',
+ 'Date range inputs arranged on a single line',
+ 'wc-admin'
+ );
+ }
+
+ getLegend( filter, config ) {
+ const date = moment(); // For now...
+ const rule = find( config.rules, { value: filter.rule } ) || {};
+ const filterStr = date && date.format( dateStringFormat );
+
+ return interpolateComponents( {
+ mixedString: config.labels.title,
+ components: {
+ filter: { filterStr },
+ rule: { rule.label },
+ },
+ } );
+ }
+
+ onSingleDateChange( { date, text, error } ) {
+ const { filter, onFilterChange } = this.props;
+ this.setState( { before: date, beforeText: text, beforeError: error } );
+
+ if ( date ) {
+ onFilterChange( filter.key, 'value', date.format( isoDateFormat ) );
+ }
+ }
+
+ onRangeDateChange( input, { date, text, error } ) {
+ const { filter, onFilterChange } = this.props;
+
+ this.setState( {
+ [ input ]: date,
+ [ input + 'Text' ]: text,
+ [ input + 'Error' ]: error,
+ } );
+
+ if ( date ) {
+ const { before, after } = this.state;
+ let nextAfter = null;
+ let nextBefore = null;
+
+ if ( 'after' === input ) {
+ nextAfter = date.format( isoDateFormat );
+ nextBefore = before ? before.format( isoDateFormat ) : null;
+ }
+
+ if ( 'before' === input ) {
+ nextAfter = after ? after.format( isoDateFormat ) : null;
+ nextBefore = date.format( isoDateFormat );
+ }
+
+ if ( nextAfter && nextBefore ) {
+ onFilterChange( filter.key, 'value', [ nextAfter, nextBefore ].join( ',' ) );
+ }
+ }
+ }
+
+ getFilterInputs() {
+ const { filter } = this.props;
+ const { before, beforeText, beforeError, after, afterText, afterError } = this.state;
+
+ if ( 'between' === filter.rule ) {
+ return interpolateComponents( {
+ mixedString: this.getBetweenString(),
+ components: {
+ after: (
+