diff --git a/client/index.js b/client/index.js index d4440f45053..5266289317a 100644 --- a/client/index.js +++ b/client/index.js @@ -10,6 +10,7 @@ import { addFilter } from '@wordpress/hooks'; import './style.scss'; import { HelloWorld } from 'hello-world'; import TransactionsPage from 'transactions'; +import 'payments-api/payments-data-store'; const DepositsPage = () => Hello from the deposits page; const DisputesPage = () => Hello from the disputes page; diff --git a/client/payments-api/api-spec/payments-rest-api.js b/client/payments-api/api-spec/payments-rest-api.js new file mode 100644 index 00000000000..10693269bed --- /dev/null +++ b/client/payments-api/api-spec/payments-rest-api.js @@ -0,0 +1,31 @@ +/** @format */ + +/** + * Internal dependencies. + */ +import transactions from './transactions'; + +function createPaymentsApiSpec() { + return { + name: 'wcPaymentsApi', + mutations: {}, + selectors: { + ...transactions.selectors, + }, + operations: { + read( resourceNames ) { + return [ + ...transactions.operations.read( resourceNames ), + ]; + }, + update( resourceNames, data ) { + return []; + }, + updateLocally( resourceNames, data ) { + return []; + }, + }, + }; +} + +export default createPaymentsApiSpec(); diff --git a/client/payments-api/api-spec/transactions/index.js b/client/payments-api/api-spec/transactions/index.js new file mode 100644 index 00000000000..024fe6a8757 --- /dev/null +++ b/client/payments-api/api-spec/transactions/index.js @@ -0,0 +1,12 @@ +/** @format */ + +/** + * Internal dependencies. + */ +import selectors from './selectors'; +import operations from './operations'; + +export default { + selectors, + operations, +}; diff --git a/client/payments-api/api-spec/transactions/operations.js b/client/payments-api/api-spec/transactions/operations.js new file mode 100644 index 00000000000..dc8b7707e69 --- /dev/null +++ b/client/payments-api/api-spec/transactions/operations.js @@ -0,0 +1,44 @@ +/** @format */ + +/** + * External dependencies. + */ +import apiFetch from '@wordpress/api-fetch'; +import { includes } from 'lodash'; + +/** + * Internal dependencies. + */ +import { NAMESPACE } from '../../constants'; + +function read( resourceNames, fetch = apiFetch, dataToResources = transactionsToResources ) { + return readTransactions( resourceNames, fetch, dataToResources ); +} + +export function readTransactions( resourceNames, fetch, dataToResources ) { + if ( includes( resourceNames, 'transactions-list' ) ) { + const url = `${ NAMESPACE }/payments/transactions`; + + return [ + fetch( { path: url } ) + .then( dataToResources ) + .catch( error => { + return { [ resourceName ]: { error } }; + } ) + ]; + } + + return []; +} + +export function transactionsToResources( transactions ) { + return { + [ 'transactions-list' ]: { + data: transactions, + } + }; +} + +export default { + read, +}; diff --git a/client/payments-api/api-spec/transactions/selectors.js b/client/payments-api/api-spec/transactions/selectors.js new file mode 100644 index 00000000000..3f7839f4c21 --- /dev/null +++ b/client/payments-api/api-spec/transactions/selectors.js @@ -0,0 +1,42 @@ +/** @format */ + +/** + * External dependencies. + */ +import { isNil } from 'lodash'; + +/** + * Internal dependencies. + */ +import { DEFAULT_REQUIREMENT } from '../../constants'; + +const getTransactions = ( getResource, requireResource ) => ( + requirement = DEFAULT_REQUIREMENT +) => { + return requireResource( requirement, 'transactions-list' ).data || {}; +} + +const isWaitingForInitialLoad = ( getResource ) => () => { + const resourceName = 'transactions-list'; + const transactionsResource = getResource( resourceName ); + + return transactionsResource.lastReceived === undefined; +} + +const getTransactionsIsLoading = ( getResource ) => () => { + const resourceName = 'transactions-list'; + const transactionsResource = getResource( resourceName ); + + return transactionsResource.lastRequested > transactionsResource.lastReceived; +} + +const showTransactionsPlaceholder = ( getResource ) => () => { + return isWaitingForInitialLoad( getResource )(); +} + +export default { + getTransactions, + getTransactionsIsLoading, + isWaitingForInitialLoad, + showTransactionsPlaceholder, +}; diff --git a/client/payments-api/constants.js b/client/payments-api/constants.js new file mode 100644 index 00000000000..84268735807 --- /dev/null +++ b/client/payments-api/constants.js @@ -0,0 +1,12 @@ + +/** + * External dependencies. + */ +import { MINUTE } from '@fresh-data/framework'; + +export const DEFAULT_REQUIREMENT = { + timeout: 1 * MINUTE, + freshness: 30 * MINUTE, +}; + +export const NAMESPACE = '/wc/v3'; diff --git a/client/payments-api/payments-data-store/create-api-client.js b/client/payments-api/payments-data-store/create-api-client.js new file mode 100644 index 00000000000..95ff21da014 --- /dev/null +++ b/client/payments-api/payments-data-store/create-api-client.js @@ -0,0 +1,45 @@ + +/** + * External dependencies. + */ +import { ApiClient } from '@fresh-data/framework'; + +/** + * Internal dependencies. + */ +import createStore from './create-store'; + +function createDataHandlers( store ) { + return { + dataRequested: resourceNames => { + store.dispatch( { + type: 'FRESH_DATA_REQUESTED', + resourceNames, + time: new Date(), + } ); + }, + dataReceived: resources => { + store.dispatch( { + type: 'FRESH_DATA_RECEIVED', + resources, + time: new Date(), + } ); + }, + }; +} + +function createApiClient( name, apiSpec ) { + const store = createStore( name ); + const dataHandlers = createDataHandlers( store ); + const apiClient = new ApiClient( apiSpec ); + apiClient.setDataHandlers( dataHandlers ); + + const storeChanged = () => { + apiClient.setState( store.getState() ); + } + store.subscribe( storeChanged ); + + return apiClient; +} + +export default createApiClient; diff --git a/client/payments-api/payments-data-store/create-store.js b/client/payments-api/payments-data-store/create-store.js new file mode 100644 index 00000000000..2d84ff5a287 --- /dev/null +++ b/client/payments-api/payments-data-store/create-store.js @@ -0,0 +1,16 @@ + +/** + * External dependencies. + */ +import { createStore } from 'redux'; + +/** + * Internal dependencies. + */ +import reducer from './reducer'; + +export default name => { + const devTools = window.__REDUX_DEVTOOLS_EXTENSION__; + + return createStore( reducer, devTools && devTools( { name: name, instanceId: name } ) ); +} diff --git a/client/payments-api/payments-data-store/index.js b/client/payments-api/payments-data-store/index.js new file mode 100644 index 00000000000..75f53c1d9c6 --- /dev/null +++ b/client/payments-api/payments-data-store/index.js @@ -0,0 +1,31 @@ + +/** + * External dependencies. + */ +import { registerGenericStore } from '@wordpress/data'; + +/** + * Internal dependencies. + */ +import createApiClient from './create-api-client'; +import paymentsApiSpec from '../api-spec/payments-rest-api'; + +if ( 'development' === process.env.NODE_ENV ) { + window.__FRESH_DATA_DEV_INFO__ = true; +} + +function createPaymentsApiStore() { + const apiClient = createApiClient( 'wc-payments-api', paymentsApiSpec ); + + return { + getSelectors: () => { + return apiClient.getSelectors(); + }, + getActions: () => { + return apiClient.getMutations(); + }, + subscribe: apiClient.subscribe, + }; +} + +registerGenericStore( 'wc-payments-api', createPaymentsApiStore() ); diff --git a/client/payments-api/payments-data-store/reducer.js b/client/payments-api/payments-data-store/reducer.js new file mode 100644 index 00000000000..40900f5a21c --- /dev/null +++ b/client/payments-api/payments-data-store/reducer.js @@ -0,0 +1,52 @@ + +const defaultState = { + resources: {}, +}; + +export default function reducer( state = defaultState, action ) { + switch ( action.type ) { + case 'FRESH_DATA_REQUESTED': + return reduceRequested( state, action ); + case 'FRESH_DATA_RECEIVED': + return reduceReceived( state, action ); + default: + return state; + } +} + +export function reduceRequested( state, action ) { + const newResources = action.resourceNames.reduce( ( resources, name ) => { + resources[ name ] = { lastRequested: action.time }; + return resources; + }, {} ); + return reduceResources( state, newResources ); +} + +export function reduceReceived( state, action ) { + const newResources = Object.keys( action.resources ).reduce( ( resources, name ) => { + const resource = { ...action.resources[ name ] }; + if ( resource.data ) { + resource.lastReceived = action.time; + } + if ( resource.error ) { + resource.lastError = action.time; + } + resources[ name ] = resource; + return resources; + }, {} ); + return reduceResources( state, newResources ); +} + +export function reduceResources( state, newResources ) { + const updatedResources = Object.keys( newResources ).reduce( + ( resources, resourceName ) => { + const resource = resources[ resourceName ]; + const newResource = newResources[ resourceName ]; + resources[ resourceName ] = { ...resource, ...newResource }; + return resources; + }, + { ...state.resources } + ); + + return { ...state, resources: updatedResources }; +} diff --git a/client/payments-api/test/api-spec/transactions/index.js b/client/payments-api/test/api-spec/transactions/index.js new file mode 100644 index 00000000000..3463a18c576 --- /dev/null +++ b/client/payments-api/test/api-spec/transactions/index.js @@ -0,0 +1,3 @@ +/** @format */ +import './operations'; +import './selectors'; diff --git a/client/payments-api/test/api-spec/transactions/operations.js b/client/payments-api/test/api-spec/transactions/operations.js new file mode 100644 index 00000000000..328607b8281 --- /dev/null +++ b/client/payments-api/test/api-spec/transactions/operations.js @@ -0,0 +1,69 @@ +/** @format */ + +/** + * Internal dependencies. + */ +import { readTransactions, transactionsToResources } from '../../../api-spec/transactions/operations'; +import { NAMESPACE } from '../../../constants'; + +describe( 'Transactions operations', () => { + describe( 'readTransactions()', () => { + const expectedUrl = `${ NAMESPACE }/payments/transactions`; + + it( 'Returns a list with one promise when correct resource names are supplied', () => { + const mockData = [ {}, {}, {} ]; + const expectedResolvedPromise = { + [ "transactions-list" ]: { + data: mockData, + }, + }; + + const mockToResources = jest.fn(); + mockToResources.mockReturnValue( expectedResolvedPromise ); + + const mockPromise = new Promise( () => mockData, () => {} ); + const expectedPromises = [ mockPromise ]; + + const mockFetch = jest.fn(); + mockFetch.mockReturnValue( mockPromise ); + + // Perform read operation. + const promises = readTransactions( [ 'transactions-list' ], mockFetch, mockToResources ); + + expect( mockFetch ).toHaveBeenCalledTimes( 1 ); + expect( mockFetch ).toHaveBeenCalledWith( { path: expectedUrl } ); + expect( promises ).toStrictEqual( expectedPromises ); + promises[0].then( result => { + expect( mockToResources ).toHaveBeenCalledTimes( 1 ); + expect( mockToResources ).toHaveBeenCalledWith( mockData ); + expect( result ).toBe( expectedResolvedPromise ); + } ); + } ); + + it( 'Returns an empty list when wrong resource names are supplied', () => { + const expected = []; + + const mockFetch = jest.fn(); + + // Perform read operation. + const promises = readTransactions( [ 'wrong', 'resource', 'names' ] ); + + expect( mockFetch ).not.toHaveBeenCalled(); + expect( promises ).toStrictEqual( expected ); + } ); + } ); + + describe( 'transactionsToResources()', () => { + it( 'Transactions list is correctly converted to resources', () => { + const mockData = [ {}, {}, {} ]; + const expected = { + [ 'transactions-list' ]: { + data: mockData, + }, + }; + + const resources = transactionsToResources( mockData ); + expect( resources ).toStrictEqual( expected ); + } ); + } ); +} ); diff --git a/client/payments-api/test/api-spec/transactions/selectors.js b/client/payments-api/test/api-spec/transactions/selectors.js new file mode 100644 index 00000000000..ae400ad07ae --- /dev/null +++ b/client/payments-api/test/api-spec/transactions/selectors.js @@ -0,0 +1,172 @@ +/** @format */ + +/** + * Internal dependencies. + */ +import transactionsSelectors from '../../../api-spec/transactions/selectors'; +import { DEFAULT_REQUIREMENT } from '../../../constants'; + + +describe( 'Transactions selectors', () => { + const expectedResourceName = 'transactions-list'; + const now = Date.now(); + const second_before_now = now - 1000; + + describe( 'getTransactions()', () => { + it( 'Returns empty list before a read operation', () => { + const expected = {}; + + const mockGetResource = jest.fn(); + const mockRequireResource = jest.fn(); + + mockRequireResource.mockReturnValue( {} ); + const transactions = transactionsSelectors.getTransactions( mockGetResource, mockRequireResource )(); + + expect( mockGetResource ).not.toHaveBeenCalled(); + expect( mockRequireResource ).toHaveBeenCalledTimes( 1 ); + expect( mockRequireResource ).toHaveBeenCalledWith( DEFAULT_REQUIREMENT, expectedResourceName ); + expect( transactions ).toStrictEqual( expected ); + } ); + + it( 'Returns the expected transactions list after a read operation', () => { + const expected = { data: [ {}, {}, {} ] }; + + const mockGetResource = jest.fn(); + const mockRequireResource = jest.fn(); + + mockRequireResource.mockReturnValue( { data: expected } ); + const transactions = transactionsSelectors.getTransactions( mockGetResource, mockRequireResource )(); + + expect( mockGetResource ).not.toHaveBeenCalled(); + expect( mockRequireResource ).toHaveBeenCalledTimes( 1 ); + expect( mockRequireResource ).toHaveBeenCalledWith( DEFAULT_REQUIREMENT, expectedResourceName ); + expect( transactions ).toBe( expected ); + } ); + } ); + + describe( 'getTransactionsIsLoading()', () => { + it( "Returns false when a read operation isn't in flight", () => { + const expected = false; + + const mockGetResource = jest.fn(); + + mockGetResource.mockReturnValue( { + lastRequested: second_before_now, + lastReceived: now, + } ); + const isLoading = transactionsSelectors.getTransactionsIsLoading( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( isLoading ).toStrictEqual( expected ); + } ); + + it( 'Returns true when a read operation is in flight', () => { + const expected = true; + + const mockGetResource = jest.fn(); + + mockGetResource.mockReturnValue( { + lastRequested: now, + lastReceived: second_before_now, + } ); + const isLoading = transactionsSelectors.getTransactionsIsLoading( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( isLoading ).toStrictEqual( expected ); + } ); + } ); + + describe( 'isWaitingForInitialLoad()', () => { + it( 'Returns false when transactions are initialized', () => { + const expected = false; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( { + lastRequested: second_before_now, + lastReceived: now, + } ); + + const initStatus = transactionsSelectors.isWaitingForInitialLoad( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( initStatus ).toBe( expected ); + } ); + + it( "Returns true when transactions aren't initialized", () => { + const expected = true; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( {} ); + + const initStatus = transactionsSelectors.isWaitingForInitialLoad( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( initStatus ).toBe( expected ); + } ); + + it( "Returns true when first request in flight", () => { + const expected = true; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( { + lastRequested: second_before_now, + } ); + + const initStatus = transactionsSelectors.isWaitingForInitialLoad( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( initStatus ).toBe( expected ); + } ); + } ); + + describe( 'showTransactionsPlaceholder()', () => { + it( "Returns true when transactions aren't initialized", () => { + const expected = true; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( {} ); + + const showPlaceholder = transactionsSelectors.showTransactionsPlaceholder( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( showPlaceholder ).toBe( expected ); + } ); + + it( "Returns true when first request in flight", () => { + const expected = true; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( { + lastRequested: second_before_now, + } ); + + const initStatus = transactionsSelectors.showTransactionsPlaceholder( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( initStatus ).toBe( expected ); + } ); + + it( "Returns false when transactions are initialized", () => { + const expected = false; + + const mockGetResource = jest.fn(); + mockGetResource.mockReturnValue( { + lastRequested: second_before_now, + lastReceived: now, + } ); + + const showPlaceholder = transactionsSelectors.showTransactionsPlaceholder( mockGetResource )(); + + expect( mockGetResource ).toHaveBeenCalledTimes( 1 ); + expect( mockGetResource ).toHaveBeenCalledWith( expectedResourceName ); + expect( showPlaceholder ).toBe( expected ); + } ); + } ); +} ); diff --git a/client/payments-api/test/index.js b/client/payments-api/test/index.js new file mode 100644 index 00000000000..8b4e515764a --- /dev/null +++ b/client/payments-api/test/index.js @@ -0,0 +1,3 @@ +/** @format */ + +import './api-spec/transactions'; diff --git a/client/payments-api/with-select.js b/client/payments-api/with-select.js new file mode 100644 index 00000000000..d330f87210a --- /dev/null +++ b/client/payments-api/with-select.js @@ -0,0 +1,195 @@ +/** + * NOTE: This is temporary code. It exists only until a version of `@wordpress/data` + * is released which supports this functionality. + * + * @todo Remove this and use `@wordpress/data` `withSelect` instead after + * this PR is merged: https://github.com/WordPress/gutenberg/pull/11460 + * + * @format + */ + +/** + * External dependencies + */ +import { isFunction } from 'lodash'; +import { Component } from '@wordpress/element'; +import isShallowEqual from '@wordpress/is-shallow-equal'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { RegistryConsumer } from '@wordpress/data'; + +/** + * Higher-order component used to inject state-derived props using registered + * selectors. + * + * @param {Function} mapSelectToProps Function called on every state change, + * expected to return object of props to + * merge with the component's own props. + * + * @return {Component} Enhanced component with merged state data props. + */ +const withSelect = mapSelectToProps => + createHigherOrderComponent( WrappedComponent => { + /** + * Default merge props. A constant value is used as the fallback since it + * can be more efficiently shallow compared in case component is repeatedly + * rendered without its own merge props. + * + * @type {Object} + */ + const DEFAULT_MERGE_PROPS = {}; + + class ComponentWithSelect extends Component { + constructor( props ) { + super( props ); + + this.onStoreChange = this.onStoreChange.bind( this ); + this.subscribe( props.registry ); + + this.onUnmounts = {}; + this.mergeProps = this.getNextMergeProps( props ); + } + + /** + * Given a props object, returns the next merge props by mapSelectToProps. + * + * @param {Object} props Props to pass as argument to mapSelectToProps. + * + * @return {Object} Props to merge into rendered wrapped element. + */ + getNextMergeProps( props ) { + const storeSelectors = {}; + const onCompletes = []; + const componentContext = { component: this }; + + const getStoreFromRegistry = ( key, registry, context ) => { + // This is our first time selecting from this store. + // Do some lazy-loading of handling at this time. + const selectorsForKey = registry.select( key ); + + if ( isFunction( selectorsForKey ) ) { + // This store has special handling for its selectors. + // We give it a context, and we check for a "resolve" + const { selectors, onComplete, onUnmount } = selectorsForKey( context ); + onComplete && onCompletes.push( onComplete ); + onUnmount && ( this.onUnmounts[ key ] = onUnmount ); + storeSelectors[ key ] = selectors; + } else { + storeSelectors[ key ] = selectorsForKey; + } + }; + + const select = key => { + if ( ! storeSelectors[ key ] ) { + // TODO: make this more functional in nature, e.g. by having + // getStoreFromRegistry return a value, and making the side + // effects from calling the function clearer? + getStoreFromRegistry( key, props.registry, componentContext ); + } + + return storeSelectors[ key ]; + }; + + const selectedProps = mapSelectToProps( select, props.ownProps ) || DEFAULT_MERGE_PROPS; + + // Complete the select for those stores which support it. + onCompletes.forEach( onComplete => onComplete() ); + return selectedProps; + } + + componentDidMount() { + this.canRunSelection = true; + + // A state change may have occurred between the constructor and + // mount of the component (e.g. during the wrapped components own + // constructor), in which case selection should be rerun. + if ( this.hasQueuedSelection ) { + this.hasQueuedSelection = false; + this.onStoreChange(); + } + } + + componentWillUnmount() { + this.canRunSelection = false; + this.unsubscribe(); + Object.keys( this.onUnmounts ).forEach( key => this.onUnmounts[ key ]() ); + } + + shouldComponentUpdate( nextProps, nextState ) { + // Cycle subscription if registry changes. + const hasRegistryChanged = nextProps.registry !== this.props.registry; + if ( hasRegistryChanged ) { + this.unsubscribe(); + this.subscribe( nextProps.registry ); + } + + // Treat a registry change as equivalent to `ownProps`, to reflect + // `mergeProps` to rendered component if and only if updated. + const hasPropsChanged = + hasRegistryChanged || ! isShallowEqual( this.props.ownProps, nextProps.ownProps ); + + // Only render if props have changed or merge props have been updated + // from the store subscriber. + if ( this.state === nextState && ! hasPropsChanged ) { + return false; + } + + if ( hasPropsChanged ) { + const nextMergeProps = this.getNextMergeProps( nextProps ); + if ( ! isShallowEqual( this.mergeProps, nextMergeProps ) ) { + // If merge props change as a result of the incoming props, + // they should be reflected as such in the upcoming render. + // While side effects are discouraged in lifecycle methods, + // this component is used heavily, and prior efforts to use + // `getDerivedStateFromProps` had demonstrated miserable + // performance. + this.mergeProps = nextMergeProps; + } + + // Regardless whether merge props are changing, fall through to + // incur the render since the component will need to receive + // the changed `ownProps`. + } + + return true; + } + + onStoreChange() { + if ( ! this.canRunSelection ) { + this.hasQueuedSelection = true; + return; + } + + const nextMergeProps = this.getNextMergeProps( this.props ); + if ( isShallowEqual( this.mergeProps, nextMergeProps ) ) { + return; + } + + this.mergeProps = nextMergeProps; + + // Schedule an update. Merge props are not assigned to state since + // derivation of merge props from incoming props occurs within + // shouldComponentUpdate, where setState is not allowed. setState + // is used here instead of forceUpdate because forceUpdate bypasses + // shouldComponentUpdate altogether, which isn't desireable if both + // state and props change within the same render. Unfortunately, + // this requires that next merge props are generated twice. + this.setState( {} ); + } + + subscribe( registry ) { + this.unsubscribe = registry.subscribe( this.onStoreChange ); + } + + render() { + return ; + } + } + + return ownProps => ( + + { registry => } + + ) + }, 'withSelect' ); + +export default withSelect; diff --git a/client/transactions/index.js b/client/transactions/index.js index c9b2200c75b..c59f5a6a2fd 100644 --- a/client/transactions/index.js +++ b/client/transactions/index.js @@ -1,14 +1,17 @@ /** * External dependencies */ -import { useState, useEffect } from '@wordpress/element'; -import apiFetch from '@wordpress/api-fetch'; import { dateI18n } from '@wordpress/date'; import moment from 'moment'; import { formatCurrency } from '@woocommerce/currency'; import { TableCard } from '@woocommerce/components'; import { capitalize } from 'lodash'; +/** + * Internal dependencies. + */ +import withSelect from 'payments-api/with-select'; + const headers = [ { key: 'created', label: 'Date / Time', required: true, isLeftAligned: true, defaultSort: true, defaultOrder: 'desc' }, { key: 'type', label: 'Type', required: true }, @@ -24,25 +27,12 @@ const headers = [ { key: 'risk_level', label: 'Risk Level', hiddenByDefault: true }, ]; -const TransactionsPage = () => { - const [ transactions, setTransactions ] = useState( [] ); - const [ loading, setLoading ] = useState( false ); - - useEffect( () => { - setLoading( true ); - apiFetch( { path: '/wc/v3/payments/transactions' } ).then( ( response ) => { - const { data } = response; - if ( data ) { - setTransactions( data ); - } else { - console.error( response ); - } +const TransactionsList = ( props ) => { + const { transactions, showPlaceholder } = props; + const transactionsData = transactions.data || []; + // Do not display table loading view if data is already available. - setLoading( false ); - } ); - }, [] ); - - const rows = transactions.map( ( txn ) => { + const rows = transactionsData.map( ( txn ) => { const charge = txn.source.object === 'charge' ? txn.source : null; const order_url = txn.order ? #{ txn.order.number } : ; @@ -50,7 +40,6 @@ const TransactionsPage = () => { const billing_details = charge ? charge.billing_details : null; const outcome = charge ? charge.outcome : null; const payment_method_details = charge ? charge.payment_method_details : null; - const address = billing_details ? billing_details.address : null; const card = payment_method_details ? payment_method_details.card : null; @@ -76,7 +65,7 @@ const TransactionsPage = () => { return ( { ); }; -export default TransactionsPage; +export default withSelect( select => { + const { getTransactions, showTransactionsPlaceholder } = select( 'wc-payments-api' ); + const transactions = getTransactions(); + const showPlaceholder = showTransactionsPlaceholder(); + + return { transactions, showPlaceholder }; +} )( TransactionsList ); diff --git a/package-lock.json b/package-lock.json index 54945ba1af9..d8bafb3ab78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -336,9 +336,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.4.tgz", - "integrity": "sha512-KCx0z3y7y8ipZUMAEEJOyNi11lMb/FOPUjjB113tfowgw0c16EGYos7worCKBcUAh2oG+OBnoUhsnTSoLpV9uA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", + "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -826,7 +826,6 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.2" } @@ -915,6 +914,14 @@ "minimist": "^1.2.0" } }, + "@fresh-data/framework": { + "version": "0.7.0-beta.0", + "resolved": "https://registry.npmjs.org/@fresh-data/framework/-/framework-0.7.0-beta.0.tgz", + "integrity": "sha512-EunC210+m/pDlF8ZmGzV0UA3UKXsP+qqaJRHU9Y/dULEW7xki0kvp6P52ykRptcYQquayUPEORn/90qBMDI/cQ==", + "requires": { + "@babel/runtime": "^7.4.2" + } + }, "@jest/console": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", @@ -1728,6 +1735,12 @@ "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", "dev": true }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, "moment": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", @@ -1792,6 +1805,14 @@ "@babel/runtime-corejs2": "7.4.5", "@woocommerce/number": "1.0.2", "lodash": "4.17.11" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } } }, "@woocommerce/date": { @@ -1821,6 +1842,12 @@ "tannin": "^1.0.1" } }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, "moment": { "version": "2.22.2", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", @@ -1863,6 +1890,12 @@ "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", "dev": true }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -1897,6 +1930,12 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true } } }, @@ -3388,6 +3427,21 @@ "type-is": "~1.6.17" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -4659,12 +4713,11 @@ "dev": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "decamelize": { @@ -5561,6 +5614,15 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -5578,6 +5640,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -5648,6 +5716,21 @@ "vary": "~1.1.2" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -5769,6 +5852,23 @@ "debug": "2.6.9", "mkdirp": "0.5.1", "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "extsprintf": { @@ -5923,6 +6023,23 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "find-cache-dir": { @@ -6019,6 +6136,23 @@ "chalk": "^2.0.1", "commander": "^2.11.0", "debug": "^2.6.8" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "find-up": { @@ -7225,10 +7359,12 @@ "dev": true }, "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", - "dev": true + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } }, "homedir-polyfill": { "version": "1.0.3", @@ -7688,7 +7824,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -8881,8 +9016,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.13.1", @@ -9148,10 +9282,9 @@ "dev": true }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "lodash._basecallback": { "version": "3.3.1", @@ -9316,7 +9449,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -9829,10 +9961,9 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stream": { "version": "0.0.7", @@ -10384,8 +10515,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -10952,6 +11082,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -11209,7 +11354,6 @@ "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -11489,6 +11633,14 @@ "dev": true, "requires": { "hoist-non-react-statics": "^2.1.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", + "dev": true + } } }, "react-dates": { @@ -11527,8 +11679,7 @@ "react-is": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -11567,6 +11718,19 @@ "prop-types": "^15.5.8" } }, + "react-redux": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-6.0.1.tgz", + "integrity": "sha512-T52I52Kxhbqy/6TEfBv85rQSDz6+Y28V/pf52vDWs1YRXG19mcFOGfHnY2HsNFHyhP+ST34Aih98fvt6tqwVcQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.8.2" + } + }, "react-router": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", @@ -11664,6 +11828,14 @@ "object.assign": "^4.1.0", "object.values": "^1.0.4", "prop-types": "^15.6.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", + "dev": true + } } }, "react-with-styles": { @@ -11799,7 +11971,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "symbol-observable": "^1.2.0" @@ -11829,8 +12000,7 @@ "regenerator-runtime": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", - "dev": true + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" }, "regenerator-transform": { "version": "0.14.0", @@ -12395,6 +12565,23 @@ "statuses": "~1.5.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12453,6 +12640,11 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -12582,6 +12774,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -12599,6 +12800,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -13473,8 +13680,7 @@ "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "symbol-tree": { "version": "3.2.4", @@ -14694,6 +14900,21 @@ "supports-color": "^2.0.0" } }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", diff --git a/package.json b/package.json index 7feae092397..6cb18a3c4c4 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,15 @@ } }, "dependencies": { - "css-loader": "^2.1.1" + "@fresh-data/framework": "^0.7.0-beta", + "css-loader": "^2.1.1", + "debug": "^4.1.1", + "lodash": "^4.17.14", + "react-redux": "^6.0.1", + "redux": "^4.0.4" }, "devDependencies": { + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", "@woocommerce/components": "^3.1.0", "@woocommerce/currency": "^1.1.2", "@woocommerce/date": "^1.2.0",