Skip to content

Instantly share code, notes, and snippets.

@ikraamg
Created May 6, 2022 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ikraamg/3fa914a5a536597ac23e41ae2ec6fe22 to your computer and use it in GitHub Desktop.
Save ikraamg/3fa914a5a536597ac23e41ae2ec6fe22 to your computer and use it in GitHub Desktop.
diff --git a/package.json b/package.json
index 4b6438c..55622ef 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@stripe/terminal-js": "^0.9.0",
"babel-plugin-styled-components": "^1.10.7",
"copy-to-clipboard": "^3.3.1",
+ "date-fns": "^2.28.0",
"dotenv": "^10.0.0",
"humps": "^2.0.1",
"magic-sdk": "^8.0.0",
diff --git a/src/components/Generic/Ticker/index.jsx b/src/components/Generic/Ticker/index.jsx
new file mode 100644
index 0000000..3bbc45a
--- /dev/null
+++ b/src/components/Generic/Ticker/index.jsx
@@ -0,0 +1,16 @@
+import { useTicker } from '~/hooks/components/use-ticker'
+import { Text } from 'theme-ui'
+
+export const Ticker = ({ futureDate, styles }) => {
+ const { days, hours, minutes, seconds } = useTicker(futureDate)
+ const tickerContents = (
+ <>
+ {days > 0 && `${days} days, `}
+ {hours > 0 && `${hours} hours, `}
+ {minutes > 0 && `${minutes} minutes, `}
+ {`${seconds} seconds`}
+ </>
+ )
+
+ return <Text sx={styles}>{tickerContents}</Text>
+}
diff --git a/src/hooks/actions/use-cart.js b/src/hooks/actions/use-cart.js
index 1341e0e..50506bb 100644
--- a/src/hooks/actions/use-cart.js
+++ b/src/hooks/actions/use-cart.js
@@ -67,6 +67,11 @@ export function useCart() {
[dispatch]
)
+ const crossSell = useMemo(
+ () => bindActionCreators(actions.crossSell, dispatch),
+ [dispatch]
+ )
+
return {
cart,
addPromoCode,
@@ -80,6 +85,7 @@ export function useCart() {
modifyGiftCards,
removeFromCart,
removePromoCode,
- subscribeProduct
+ subscribeProduct,
+ crossSell,
}
}
diff --git a/src/hooks/components/use-ticker.js b/src/hooks/components/use-ticker.js
new file mode 100644
index 0000000..82a8254
--- /dev/null
+++ b/src/hooks/components/use-ticker.js
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+import { intervalToDuration, isBefore } from 'date-fns';
+
+export const useTicker = (futureDate) => {
+ const [now, setNow] = useState(new Date());
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setNow(new Date());
+ }, 1000);
+
+ return () => {
+ clearInterval(interval);
+ };
+ }, [futureDate]);
+
+ const isTimeUp = isBefore(futureDate, now);
+
+ if (isTimeUp) {
+ return { days: 0, hours: 0, minutes: 0, seconds: 0, isTimeUp };
+ }
+
+ let { days, hours, minutes, seconds } = intervalToDuration({
+ start: now,
+ end: futureDate
+ });
+
+ return { days, hours, minutes, seconds, isTimeUp };
+};
\ No newline at end of file
diff --git a/src/redux/actions/cart/cross-sell.js b/src/redux/actions/cart/cross-sell.js
new file mode 100644
index 0000000..be506df
--- /dev/null
+++ b/src/redux/actions/cart/cross-sell.js
@@ -0,0 +1,33 @@
+import {
+ CROSS_SELL_ERROR,
+ CROSS_SELL_SUCCESS,
+ CROSS_SELL_REQUEST
+} from '~/redux/actions/types'
+
+export const crossSellRequest = lineItemsAttributes => ({
+ type: CROSS_SELL_REQUEST,
+ data: lineItemsAttributes
+})
+
+export const crossSellSuccess = (response) => ({
+ type: CROSS_SELL_SUCCESS,
+ data: response
+})
+
+export const crossSellError = (error, meta = {}) => ({
+ type: CROSS_SELL_ERROR,
+ error: true,
+ meta,
+ payload: error
+})
+
+export const crossSell = (cart, lineItemsAttributes) =>
+ async (dispatch, { api }) => {
+ try {
+ dispatch(crossSellRequest(lineItemsAttributes))
+ const response = await api.crossSell(cart, lineItemsAttributes)
+ dispatch(crossSellSuccess(response))
+ } catch (error) {
+ dispatch(crossSellError(error))
+ }
+ }
diff --git a/src/redux/actions/cart/index.js b/src/redux/actions/cart/index.js
index a4ac1dc..30b602e 100644
--- a/src/redux/actions/cart/index.js
+++ b/src/redux/actions/cart/index.js
@@ -16,3 +16,4 @@ export * from './get-user-wallet'
export * from './update-order-addresses'
export * from './update-order-delivery'
export * from './update-order-payment'
+export * from './cross-sell'
diff --git a/src/redux/actions/types.js b/src/redux/actions/types.js
index 2fad0de..d5490f0 100644
--- a/src/redux/actions/types.js
+++ b/src/redux/actions/types.js
@@ -108,6 +108,9 @@ export const UPDATE_ORDER_PAYMENT_ERROR = 'UPDATE_ORDER_PAYMENT_ERROR'
export const SEED_NOTIFICATIONS = 'SEED_NOTIFICATIONS'
export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'
export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION'
+export const CROSS_SELL_ERROR = 'CROSS_SELL_ERROR'
+export const CROSS_SELL_SUCCESS = 'CROSS_SELL_SUCCESS'
+export const CROSS_SELL_REQUEST = 'CROSS_SELL_REQUEST'
const status = {
Idle: 'idle',
diff --git a/src/redux/reducers/cart.js b/src/redux/reducers/cart.js
index 6ed44b4..e4c9309 100644
--- a/src/redux/reducers/cart.js
+++ b/src/redux/reducers/cart.js
@@ -40,7 +40,10 @@ import {
UPDATE_ORDER_DELIVERY_ERROR,
UPDATE_ORDER_PAYMENT_REQUEST,
UPDATE_ORDER_PAYMENT_SUCCESS,
- UPDATE_ORDER_PAYMENT_ERROR
+ UPDATE_ORDER_PAYMENT_ERROR,
+ CROSS_SELL_ERROR,
+ CROSS_SELL_SUCCESS,
+ CROSS_SELL_REQUEST,
} from '~/redux/actions/types'
import initialState from '~/redux/store/initial-state'
@@ -54,6 +57,7 @@ const cart = (state = initialState.cart, action) => {
case UPDATE_ORDER_ADDRESSES_REQUEST:
case UPDATE_ORDER_DELIVERY_REQUEST:
case UPDATE_ORDER_PAYMENT_REQUEST:
+ case CROSS_SELL_REQUEST:
return {
...state,
isFetching: true,
@@ -80,6 +84,7 @@ const cart = (state = initialState.cart, action) => {
case UPDATE_ORDER_ADDRESSES_SUCCESS:
case UPDATE_ORDER_DELIVERY_SUCCESS:
case UPDATE_ORDER_PAYMENT_SUCCESS:
+ case CROSS_SELL_SUCCESS:
return {
...state,
isFetching: false,
@@ -103,6 +108,7 @@ const cart = (state = initialState.cart, action) => {
case UPDATE_ORDER_ADDRESSES_ERROR:
case UPDATE_ORDER_DELIVERY_ERROR:
case UPDATE_ORDER_PAYMENT_ERROR:
+ case CROSS_SELL_ERROR:
return {
...state,
error: action.payload,
diff --git a/src/redux/reducers/cart.spec.js b/src/redux/reducers/cart.spec.js
index 190f8a2..53c1c34 100644
--- a/src/redux/reducers/cart.spec.js
+++ b/src/redux/reducers/cart.spec.js
@@ -40,7 +40,10 @@ import {
UPDATE_ORDER_DELIVERY_ERROR,
UPDATE_ORDER_PAYMENT_REQUEST,
UPDATE_ORDER_PAYMENT_SUCCESS,
- UPDATE_ORDER_PAYMENT_ERROR
+ UPDATE_ORDER_PAYMENT_ERROR,
+ CROSS_SELL_ERROR,
+ CROSS_SELL_SUCCESS,
+ CROSS_SELL_REQUEST
} from '~/redux/actions/types'
import initialState from '~/redux/store/initial-state'
import { mockedFullCart } from '~/__mocks__/acs/cart_full'
@@ -726,4 +729,50 @@ describe('cart reducer', () => {
error
})
})
+
+ it('should handle CROSS_SELL_REQUEST', () => {
+ expect(
+ cart(
+ {},
+ {
+ type: CROSS_SELL_REQUEST
+ }
+ )
+ ).toEqual({
+ isFetching: true,
+ error: null
+ })
+ })
+
+ it('should handle CROSS_SELL_SUCCESS', () => {
+ expect(
+ cart(
+ {},
+ {
+ type: CROSS_SELL_SUCCESS,
+ data: {}
+ }
+ )
+ ).toEqual({
+ data: {},
+ isFetching: false,
+ error: null
+ })
+ })
+
+ it('should handle CROSS_SELL_ERROR', () => {
+ expect(
+ cart(
+ {},
+ {
+ type: CROSS_SELL_ERROR,
+ error: true,
+ payload: error
+ }
+ )
+ ).toEqual({
+ isFetching: false,
+ error
+ })
+ })
})
diff --git a/src/services/api/index.js b/src/services/api/index.js
index b24f5fe..1c6d4c0 100644
--- a/src/services/api/index.js
+++ b/src/services/api/index.js
@@ -386,6 +386,25 @@ class Api {
const response = await axiosClient.post(url, props, config)
return response.data
}
+
+ async crossSell(cart, lineItemsAttributes) {
+ const url = `/api/orders/${cart.number}/cross_sell`
+ const config = {
+ transformRequest: [
+ (data, headers) => {
+ headers['X-Spree-Order-Token'] = cart.token
+ return JSON.stringify(data)
+ }
+ ]
+ }
+ const payload = {
+ order: {
+ line_items_attributes: lineItemsAttributes
+ }
+ }
+ const response = await axiosClient.put(url, payload, config)
+ return response.data
+ }
}
const api = new Api()
diff --git a/src/services/api/index.test.js b/src/services/api/index.test.js
index b4f722a..53f4933 100644
--- a/src/services/api/index.test.js
+++ b/src/services/api/index.test.js
@@ -103,6 +103,26 @@ describe('api', () => {
})
})
+ describe('crossSell', () => {
+ const cart = { number: 'CART-ID', token: "test" }
+ const lineItemsAttributes = [{ "sku": "product-4", "quantity": 8 }]
+ const payload = { order: { line_items_attributes: lineItemsAttributes } }
+
+ it('puts the request to the cart endpoint', () => {
+ axios.put = jest.fn().mockImplementation(() => {
+ return {}
+ })
+
+ api.crossSell(cart, lineItemsAttributes)
+
+ expect(axios.put).toHaveBeenCalledWith(
+ `/api/orders/${cart.number}/cross_sell`, payload, {
+ transformRequest: expect.any(Array)
+ }
+ )
+ })
+ })
+
describe('getStates', () => {
it('calls the states endpoint', async () => {
const iso = 'US'
diff --git a/yarn.lock b/yarn.lock
index dda9b99..389878a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3519,6 +3519,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
+date-fns@^2.28.0:
+ version "2.28.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
+ integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
+
debug@4, debug@^4.1.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment