@akinon/next 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ZERO-2297: Add landing page structure
8
+ - ZERO-2317: Prevent browser cache using timestamp
9
+ - ZERO-2293: Upgrade Redux
10
+ - ZERO-2285: Allow custom headers in server data fetch functions
11
+ - ZERO-1577: Add option to exclude payment options from iframe
12
+ - ZERO-2282: Allow custom headers in client proxy API
13
+ - ZERO-2308: Add B2B endpoints and types
14
+ - ZERO-2202: Ensure commerce endpoints end with trailing slash
15
+
3
16
  ## 1.8.0
4
17
 
5
18
  ### Minor Changes
package/api/client.ts CHANGED
@@ -29,7 +29,7 @@ async function proxyRequest(...args) {
29
29
  responseType: 'json'
30
30
  };
31
31
 
32
- const slug = params.slug.join('/');
32
+ const slug = `${params.slug.join('/')}/`;
33
33
  const options_ = JSON.parse(
34
34
  decodeURIComponent((searchParams.get('options') as string) ?? '{}')
35
35
  );
@@ -37,23 +37,34 @@ async function proxyRequest(...args) {
37
37
 
38
38
  Object.assign(options, options_);
39
39
 
40
- Array.from(searchParams.keys()).forEach((key) => {
41
- if (key !== 'slug' && key !== 'options') {
40
+ Array.from(searchParams.keys())
41
+ .filter((key) => !['slug', 'options'].includes(key))
42
+ .forEach((key) => {
42
43
  urlSearchParams.append(key, `${searchParams.get(key)}`);
43
- }
44
- });
44
+ });
45
+
46
+ const extraHeaders = Object.fromEntries(req.headers.entries());
47
+
48
+ [
49
+ 'x-forwarded-host',
50
+ 'x-forwarded-for',
51
+ 'x-forwarded-proto',
52
+ 'x-forwarded-port',
53
+ 'origin',
54
+ 'host',
55
+ 'referer',
56
+ 'accept',
57
+ 'content-length',
58
+ 'content-type'
59
+ ].forEach((header) => delete extraHeaders[header]);
45
60
 
46
61
  const fetchOptions = {
47
62
  method: req.method,
48
63
  headers: {
64
+ ...extraHeaders,
49
65
  'X-Requested-With': 'XMLHttpRequest',
50
- 'X-CSRFToken': `${req.headers.get('x-csrftoken')}`,
51
- 'x-currency': `${req.headers.get('x-currency')}`,
52
- 'Accept-Language': `${req.headers.get('Accept-Language')}`,
53
- 'User-Agent': `${req.headers.get('User-Agent')}`,
54
66
  Referer: commerceUrl,
55
- Accept: options.accept,
56
- Cookie: `${req.headers.get('cookie')}`
67
+ Accept: options.accept
57
68
  }
58
69
  } as RequestInit;
59
70
 
@@ -68,7 +79,13 @@ async function proxyRequest(...args) {
68
79
  try {
69
80
  body = await req.json();
70
81
  } catch (error) {
71
- console.log(error);
82
+ logger.error(
83
+ `Client Proxy Request - Error while parsing request body to JSON`,
84
+ {
85
+ url: req.url,
86
+ error
87
+ }
88
+ );
72
89
  }
73
90
 
74
91
  Object.keys(body ?? {}).forEach((key) => {
@@ -82,8 +99,8 @@ async function proxyRequest(...args) {
82
99
 
83
100
  let url = `${commerceUrl}/${slug.replace(/,/g, '/')}`;
84
101
 
85
- if (options.useTrailingSlash) {
86
- url += '/';
102
+ if (!options.useTrailingSlash) {
103
+ url = url.replace(/\/$/, '');
87
104
  }
88
105
 
89
106
  if (urlSearchParams.toString().length) {
@@ -7,7 +7,8 @@ import {
7
7
  AccountOrderCancellation,
8
8
  AccountOrderCancellationReason,
9
9
  ContactFormType,
10
- Order
10
+ Order,
11
+ Quotations
11
12
  } from '../../types';
12
13
 
13
14
  interface GetOrdersResponse {
@@ -21,6 +22,11 @@ interface GetOrdersParams {
21
22
  createdDate?: string;
22
23
  }
23
24
 
25
+ export interface GetQuotationsResponse {
26
+ count: number;
27
+ results: Quotations[];
28
+ }
29
+
24
30
  export type PasswordResetType = {
25
31
  password: string;
26
32
  repeatPassword: string;
@@ -102,6 +108,9 @@ const accountApi = api.injectEndpoints({
102
108
  query: ({ page, limit, createdDate } = {}) =>
103
109
  buildClientRequestUrl(account.getOrders({ page, limit, createdDate }))
104
110
  }),
111
+ getQuotations: builder.query<GetQuotationsResponse, void>({
112
+ query: () => buildClientRequestUrl(account.getQuotations)
113
+ }),
105
114
  sendContact: builder.mutation<void, ContactFormType>({
106
115
  query: (body) => ({
107
116
  url: buildClientRequestUrl(account.sendContact, {
@@ -170,6 +179,7 @@ export const {
170
179
  useGetContactSubjectsQuery,
171
180
  useGetOrderQuery,
172
181
  useGetOrdersQuery,
182
+ useGetQuotationsQuery,
173
183
  useSendContactMutation,
174
184
  useUpdateEmailMutation,
175
185
  useUpdatePasswordMutation,
@@ -0,0 +1,32 @@
1
+ import { buildClientRequestUrl } from '../../utils';
2
+ import { api } from './api';
3
+ import { b2b } from '../urls';
4
+ import { Basket, BasketParams, BasketResponse, Division, GetResponse } from '../../types';
5
+
6
+ const b2bApi = api.injectEndpoints({
7
+ endpoints: (build) => ({
8
+ getBasket: build.query<Basket, void>({
9
+ query: () =>
10
+ buildClientRequestUrl(b2b.basket, {
11
+ contentType: 'application/json'
12
+ }),
13
+ transformResponse: (response: { basket: Basket }) => response.basket,
14
+ providesTags: ['Basket']
15
+ }),
16
+ getDivisions: build.query<GetResponse<Division>, void>({
17
+ query: () => buildClientRequestUrl(b2b.divisions)
18
+ }),
19
+ addToBasket: build.mutation<BasketResponse, BasketParams>({
20
+ query: (body) => ({
21
+ url: buildClientRequestUrl(b2b.basket, {
22
+ contentType: 'application/json'
23
+ }),
24
+ method: 'POST',
25
+ body
26
+ })
27
+ }),
28
+ }),
29
+ overrideExisting: true
30
+ });
31
+
32
+ export const { useGetBasketQuery, useLazyGetDivisionsQuery, useAddToBasketMutation } = b2bApi;
@@ -39,7 +39,6 @@ export const basketApi = api.injectEndpoints({
39
39
  clearBasket: build.mutation<Basket, void>({
40
40
  query: (body) => ({
41
41
  url: buildClientRequestUrl(basket.getBasket, {
42
- useTrailingSlash: false,
43
42
  contentType: 'application/json'
44
43
  }),
45
44
  method: 'DELETE',
@@ -51,7 +50,6 @@ export const basketApi = api.injectEndpoints({
51
50
  applyVoucherCode: build.mutation<Basket, { voucher_code: string }>({
52
51
  query: (body) => ({
53
52
  url: buildClientRequestUrl(basket.getBasket, {
54
- useTrailingSlash: false,
55
53
  contentType: 'application/json'
56
54
  }),
57
55
  method: 'PATCH',
@@ -62,7 +60,6 @@ export const basketApi = api.injectEndpoints({
62
60
  removeVoucherCode: build.mutation<Basket, void>({
63
61
  query: () => ({
64
62
  url: buildClientRequestUrl(basket.getBasket, {
65
- useTrailingSlash: false,
66
63
  contentType: 'application/json'
67
64
  }),
68
65
  method: 'PATCH',
@@ -77,7 +77,7 @@ export const miscApi = api.injectEndpoints({
77
77
  }),
78
78
  getWidget: builder.query<WidgetResultType<any>, string>({
79
79
  query: (slug: string) => ({
80
- url: buildClientRequestUrl(widgets.getWidgets(slug))
80
+ url: buildClientRequestUrl(widgets.getWidget(slug))
81
81
  })
82
82
  }),
83
83
  getMenu: builder.query<MenuItemType[], number>({
@@ -6,7 +6,11 @@ import { Cache, CacheKey } from '../../lib/cache';
6
6
  import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
8
 
9
- function getCategoryDataHandler(pk: number, searchParams?: URLSearchParams) {
9
+ function getCategoryDataHandler(
10
+ pk: number,
11
+ searchParams?: URLSearchParams,
12
+ headers?: Record<string, string>
13
+ ) {
10
14
  return async function () {
11
15
  const params = generateCommerceSearchParams(searchParams);
12
16
 
@@ -15,7 +19,8 @@ function getCategoryDataHandler(pk: number, searchParams?: URLSearchParams) {
15
19
  {
16
20
  headers: {
17
21
  Accept: 'application/json',
18
- 'Content-Type': 'application/json'
22
+ 'Content-Type': 'application/json',
23
+ ...(headers ?? {})
19
24
  }
20
25
  },
21
26
  FetchResponseType.TEXT
@@ -59,14 +64,16 @@ function getCategoryDataHandler(pk: number, searchParams?: URLSearchParams) {
59
64
 
60
65
  export const getCategoryData = ({
61
66
  pk,
62
- searchParams
67
+ searchParams,
68
+ headers
63
69
  }: {
64
70
  pk: number;
65
71
  searchParams?: URLSearchParams;
72
+ headers?: Record<string, string>;
66
73
  }) => {
67
74
  return Cache.wrap(
68
- CacheKey.Category(pk, searchParams),
69
- getCategoryDataHandler(pk, searchParams),
75
+ CacheKey.Category(pk, searchParams, headers),
76
+ getCategoryDataHandler(pk, searchParams, headers),
70
77
  {
71
78
  expire: 300
72
79
  }
@@ -75,7 +82,7 @@ export const getCategoryData = ({
75
82
 
76
83
  function getCategoryBySlugDataHandler(slug: string) {
77
84
  return async function () {
78
- let rawData = await appFetch<string>(
85
+ const rawData = await appFetch<string>(
79
86
  category.getCategoryBySlug(slug),
80
87
  {
81
88
  headers: {
@@ -6,3 +6,4 @@ export * from './special-page';
6
6
  export * from './widget';
7
7
  export * from './seo';
8
8
  export * from './menu';
9
+ export * from './landingpage';
@@ -0,0 +1,24 @@
1
+ import { landingpage } from '../urls';
2
+ import { LandingPage } from '../../types/commerce/landingpage';
3
+ import appFetch from '../../utils/app-fetch';
4
+ import { Cache, CacheKey } from '../../lib/cache';
5
+
6
+ const getLandingPageHandler = (pk: number) => {
7
+ return async function () {
8
+ const data = await appFetch<LandingPage>(
9
+ landingpage.getLandingPageByPk(pk),
10
+ {
11
+ headers: {
12
+ Accept: 'application/json',
13
+ 'Content-Type': 'application/json'
14
+ }
15
+ }
16
+ );
17
+
18
+ return data;
19
+ };
20
+ };
21
+
22
+ export const getLandingPageData = ({ pk }: { pk: number }) => {
23
+ return Cache.wrap(CacheKey.LandingPage(pk), getLandingPageHandler(pk));
24
+ };
@@ -6,7 +6,10 @@ import appFetch, { FetchResponseType } from '../../utils/app-fetch';
6
6
  import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
8
 
9
- const getListDataHandler = (searchParams: URLSearchParams) => {
9
+ const getListDataHandler = (
10
+ searchParams: URLSearchParams,
11
+ headers?: Record<string, string>
12
+ ) => {
10
13
  return async function () {
11
14
  const params = generateCommerceSearchParams(searchParams);
12
15
 
@@ -15,7 +18,8 @@ const getListDataHandler = (searchParams: URLSearchParams) => {
15
18
  {
16
19
  headers: {
17
20
  Accept: 'application/json',
18
- 'Content-Type': 'application/json'
21
+ 'Content-Type': 'application/json',
22
+ ...(headers ?? {})
19
23
  }
20
24
  },
21
25
  FetchResponseType.TEXT
@@ -42,13 +46,15 @@ const getListDataHandler = (searchParams: URLSearchParams) => {
42
46
  };
43
47
 
44
48
  export const getListData = async ({
45
- searchParams
49
+ searchParams,
50
+ headers
46
51
  }: {
47
52
  searchParams: URLSearchParams;
53
+ headers?: Record<string, string>;
48
54
  }) => {
49
55
  return Cache.wrap(
50
- CacheKey.List(searchParams),
51
- getListDataHandler(searchParams),
56
+ CacheKey.List(searchParams, headers),
57
+ getListDataHandler(searchParams, headers),
52
58
  {
53
59
  expire: 300
54
60
  }
@@ -29,7 +29,7 @@ function getSeoDataHandler(url: string) {
29
29
  try {
30
30
  data = await appFetch(misc.cmsSeo(url));
31
31
  } catch (error) {
32
- logger.error('Error while fetching seo data', { url, error });
32
+ // logger.error('Error while fetching seo data', { url, error });
33
33
  }
34
34
 
35
35
  return data;
@@ -3,10 +3,12 @@ import { category } from '../urls';
3
3
  import { GetCategoryResponse } from '../../types';
4
4
  import { generateCommerceSearchParams } from '../../utils';
5
5
  import appFetch from '../../utils/app-fetch';
6
+ import header from '../../redux/reducers/header';
6
7
 
7
8
  const getSpecialPageDataHandler = (
8
9
  pk: number,
9
- searchParams: URLSearchParams
10
+ searchParams: URLSearchParams,
11
+ headers?: Record<string, string>
10
12
  ) => {
11
13
  return async function () {
12
14
  const params = generateCommerceSearchParams(searchParams);
@@ -16,7 +18,8 @@ const getSpecialPageDataHandler = (
16
18
  {
17
19
  headers: {
18
20
  Accept: 'application/json',
19
- 'Content-Type': 'application/json'
21
+ 'Content-Type': 'application/json',
22
+ ...(headers ?? {})
20
23
  }
21
24
  }
22
25
  );
@@ -27,14 +30,16 @@ const getSpecialPageDataHandler = (
27
30
 
28
31
  export const getSpecialPageData = async ({
29
32
  pk,
30
- searchParams
33
+ searchParams,
34
+ headers
31
35
  }: {
32
36
  pk: number;
33
37
  searchParams: URLSearchParams;
38
+ headers?: Record<string, string>;
34
39
  }) => {
35
40
  return Cache.wrap(
36
- CacheKey.SpecialPage(pk, searchParams),
37
- getSpecialPageDataHandler(pk, searchParams),
41
+ CacheKey.SpecialPage(pk, searchParams, headers),
42
+ getSpecialPageDataHandler(pk, searchParams, headers),
38
43
  {
39
44
  expire: 300
40
45
  }
@@ -9,7 +9,7 @@ const getWidgetDataHandler = (slug: string) => async () => {
9
9
  return null;
10
10
  }
11
11
 
12
- return await appFetch(widgets.getWidgets(slug));
12
+ return await appFetch(widgets.getWidget(slug));
13
13
  };
14
14
 
15
15
  export const getWidgetData = async <T>({
package/data/urls.ts CHANGED
@@ -1,16 +1,17 @@
1
+ /* eslint-disable @akinon/projectzero/client-url */
1
2
  import Settings from 'settings';
2
3
 
3
4
  const API_URL = Settings.commerceUrl;
4
5
 
5
6
  export const account = {
6
- order: '/users/orders',
7
- orderId: (id: string | string[]) => `/users/orders/${id}`,
8
- cancelOrder: (id: string) => `/orders/${id}/cancel`,
9
- cancellationReasons: '/users/orders/cancellation_reasons',
10
- updatePassword: '/users/password/change',
11
- updateEmail: '/users/email-change',
12
- updateProfile: '/users/profile',
13
- getContactSubjects: '/users/contact-us-subjects',
7
+ order: '/users/orders/',
8
+ orderId: (id: string | string[]) => `/users/orders/${id}/`,
9
+ cancelOrder: (id: string) => `/orders/${id}/cancel/`,
10
+ cancellationReasons: '/users/orders/cancellation_reasons/',
11
+ updatePassword: '/users/password/change/',
12
+ updateEmail: '/users/email-change/',
13
+ updateProfile: '/users/profile/',
14
+ getContactSubjects: '/users/contact-us-subjects/',
14
15
  getOrders: ({
15
16
  page,
16
17
  limit,
@@ -20,9 +21,10 @@ export const account = {
20
21
  limit?: number;
21
22
  createdDate?: string;
22
23
  }) =>
23
- `/users/orders?page=${page || 1}&limit=${limit || 12} ${
24
+ `/users/orders/?page=${page || 1}&limit=${limit || 12} ${
24
25
  createdDate ? `&created_date__gte=${createdDate}` : ''
25
26
  }`,
27
+ getQuotations: '/b2b/my-quotations',
26
28
  sendContact: '/users/contact-us',
27
29
  passwordReset: (slug: string) => `/users/reset/${slug}`,
28
30
  anonymize: '/users/anonymize/',
@@ -31,95 +33,100 @@ export const account = {
31
33
  };
32
34
 
33
35
  export const address = {
34
- base: '/address',
35
- countries: '/address/country',
36
- getAddresses: '/address/detailed',
37
- getCities: (country: string) => `/address/city?country=${country}`,
38
- getTownships: (city: string) => `/address/township?city=${city}`,
39
- getDistricts: (township: string) => `/address/district?township=${township}`,
40
- getRetailStore: '/address/city?retailstore__isnull=false',
36
+ base: '/address/',
37
+ countries: '/address/country/',
38
+ getAddresses: '/address/detailed/',
39
+ getCities: (country: string) => `/address/city/?country=${country}`,
40
+ getTownships: (city: string) => `/address/township/?city=${city}`,
41
+ getDistricts: (township: string) => `/address/district/?township=${township}`,
42
+ getRetailStore: '/address/city/?retailstore__isnull=false',
41
43
  getRetailStoreCities: (country: string) =>
42
- `/address/city?retailstore__isnull=false&country=${country}`,
44
+ `/address/city/?retailstore__isnull=false&country=${country}`,
43
45
  getRetailStoreTownships: (city: string) =>
44
- `/address/township?retailstore__isnull=false&city=${city}`,
45
- editAddress: (pk: number) => `/address/${pk}`,
46
- removeAddress: (id: number) => `/address/${id}`,
47
- setDefaultAddress: (pk: number) => `/address/${pk}`
46
+ `/address/township/?retailstore__isnull=false&city=${city}`,
47
+ editAddress: (pk: number) => `/address/${pk}/`,
48
+ removeAddress: (id: number) => `/address/${id}/`,
49
+ setDefaultAddress: (pk: number) => `/address/${pk}/`
48
50
  };
49
51
 
50
52
  export const basket = {
51
- getBasket: '/baskets/basket'
53
+ getBasket: '/baskets/basket/'
52
54
  };
53
55
 
54
56
  export const category = {
55
- // eslint-disable-next-line projectzero/client-url
56
57
  list: '/list/',
57
58
  getCategoryByPk: (pk: number) => `/category/${pk}/`,
58
- getCategoryBySlug: (slug: string) => `/${slug}`,
59
+ getCategoryBySlug: (slug: string) => `/${slug}/`,
59
60
  getSpecialPageByPk: (pk: number) => `/special-page/${pk}/`
60
61
  };
61
62
 
62
63
  export const checkout = {
63
- get3dRedirectForm: '/orders/redirect-three-d',
64
- fetchCheckout: '/orders/checkout',
64
+ get3dRedirectForm: '/orders/redirect-three-d/',
65
+ fetchCheckout: '/orders/checkout/',
65
66
  fetchCheckoutResult: (token: string) =>
66
- `/orders/completed/${token}?format=json`,
67
- getContract: (slug: string) => `/orders/contract?contract_name=${slug}`,
68
- completeCreditCardPayment: '/orders/checkout?page=CreditCardConfirmationPage',
69
- completeFundsTransfer: '/orders/checkout?page=FundsTransferPage',
70
- guestLogin: '/orders/checkout?page=IndexPage',
71
- setDeliveryOption: '/orders/checkout?page=DeliveryOptionSelectionPage',
72
- setAddresses: '/orders/checkout?page=AddressSelectionPage',
73
- setShippingOption: '/orders/checkout?page=ShippingOptionSelectionPage',
74
- setPaymentOption: '/orders/checkout?page=PaymentOptionSelectionPage',
75
- setBinNumber: '/orders/checkout?page=BinNumberPage',
76
- setMasterpassBinNumber: '/orders/checkout?page=MasterpassBinNumberPage',
77
- setInstallmentOption: '/orders/checkout?page=InstallmentSelectionPage',
67
+ `/orders/completed/${token}/?format=json`,
68
+ getContract: (slug: string) => `/orders/contract/?contract_name=${slug}`,
69
+ completeCreditCardPayment:
70
+ '/orders/checkout/?page=CreditCardConfirmationPage',
71
+ completeFundsTransfer: '/orders/checkout/?page=FundsTransferPage',
72
+ guestLogin: '/orders/checkout/?page=IndexPage',
73
+ setDeliveryOption: '/orders/checkout/?page=DeliveryOptionSelectionPage',
74
+ setAddresses: '/orders/checkout/?page=AddressSelectionPage',
75
+ setShippingOption: '/orders/checkout/?page=ShippingOptionSelectionPage',
76
+ setPaymentOption: '/orders/checkout/?page=PaymentOptionSelectionPage',
77
+ setBinNumber: '/orders/checkout/?page=BinNumberPage',
78
+ setMasterpassBinNumber: '/orders/checkout/?page=MasterpassBinNumberPage',
79
+ setInstallmentOption: '/orders/checkout/?page=InstallmentSelectionPage',
78
80
  setMasterPassInstallmentOption:
79
- '/orders/checkout?page=MasterpassInstallmentPage',
80
- setFundsTransferOption: '/orders/checkout?page=FundsTransferChoicePage',
81
- getCheckoutProviders: '/orders/checkout?page=CheckoutProviderIndexPage',
82
- setCheckoutProvider: '/orders/checkout?page=CheckoutProviderSelectionPage',
81
+ '/orders/checkout/?page=MasterpassInstallmentPage',
82
+ setFundsTransferOption: '/orders/checkout/?page=FundsTransferChoicePage',
83
+ getCheckoutProviders: '/orders/checkout-provider-list/',
84
+ getCheckoutProvidersIndex: '/orders/checkout/?page=CheckoutProviderIndexPage',
85
+ setCheckoutProvider: '/orders/checkout/?page=CheckoutProviderSelectionPage',
83
86
  completeRedirectionPayment:
84
- '/orders/checkout?page=RedirectionPaymentSelectedPage',
87
+ '/orders/checkout/?page=RedirectionPaymentSelectedPage',
85
88
  confirmationPaymentSelect:
86
- '/orders/checkout?page=ConfirmationPaymentSelectedPage',
87
- confirmationQuery: '/orders/checkout?page=ConfirmationPaymentQueryPage',
88
- confirmationComplete: '/orders/checkout?page=ConfirmationPaymentCompletePage',
89
- completeLoyaltyPayment: '/orders/checkout?page=LoyaltyPaymentSelectedPage',
90
- loyaltyMoneyUsage: '/orders/checkout?page=LoyaltyMoneyUsagePage',
91
- setOrderNote: '/orders/checkout?page=OrderNotePage'
89
+ '/orders/checkout/?page=ConfirmationPaymentSelectedPage',
90
+ confirmationQuery: '/orders/checkout/?page=ConfirmationPaymentQueryPage',
91
+ confirmationComplete:
92
+ '/orders/checkout/?page=ConfirmationPaymentCompletePage',
93
+ completeLoyaltyPayment: '/orders/checkout/?page=LoyaltyPaymentSelectedPage',
94
+ loyaltyMoneyUsage: '/orders/checkout/?page=LoyaltyMoneyUsagePage',
95
+ setOrderNote: '/orders/checkout/?page=OrderNotePage'
92
96
  };
93
97
 
94
98
  export const flatpage = {
95
99
  getFlatPageByPk: (pk: number) => `/shop-flat-page/${pk}/`
96
100
  };
97
101
 
102
+ export const landingpage = {
103
+ getLandingPageByPk: (pk: number) => `/landing-page/${pk}/`
104
+ };
105
+
98
106
  export const misc = {
99
- autocomplete: '/autocomplete',
100
- stores: '/stores',
101
- menu: '/misc/menus/generate',
107
+ autocomplete: '/autocomplete/',
108
+ stores: '/stores/',
109
+ menu: '/misc/menus/generate/',
102
110
  prettyUrl: (pathname: string) => `/misc/pretty-url${pathname}/`,
103
111
  prettyUrls: (pathname: string) => `/pretty_urls/?new_path__exact=${pathname}`,
104
- emailSubscription: '/email-subscription',
112
+ emailSubscription: '/email-subscription/',
105
113
  menus: (depthHeight: number, parent?: string) =>
106
114
  `/menus/generate/?depth_height=${depthHeight}${
107
115
  parent ? `&parent=${parent}` : ''
108
116
  }`,
109
- // eslint-disable-next-line projectzero/client-url
110
117
  cmsSeo: (slug: string | string[]) => `/cms/seo/?url=${slug ? slug : '/'}`,
111
- setCurrency: '/users/activate-currency'
118
+ setCurrency: '/users/activate-currency/'
112
119
  };
113
120
 
114
121
  export const product = {
115
122
  category: (productId: number) => `/product/${productId}/category/`,
116
- installments: (productId: number) => `/payments/cards/product/${productId}`,
117
- getProductByPk: (pk: number) => `/product/${pk}`,
118
- getGroupProductByPk: (pk: number) => `/group-product/${pk}`,
123
+ installments: (productId: number) => `/payments/cards/product/${productId}/`,
124
+ getProductByPk: (pk: number) => `/product/${pk}/`,
125
+ getGroupProductByPk: (pk: number) => `/group-product/${pk}/`,
119
126
  getRetailStoreStock: (productPk: string, queryString: string) =>
120
- `/retail_store_stock/${productPk}?${queryString}`,
121
- addProduct: '/baskets/basket',
122
- slug: (slug: string) => `/${slug}`,
127
+ `/retail_store_stock/${productPk}/?${queryString}`,
128
+ addProduct: '/baskets/basket/',
129
+ slug: (slug: string) => `/${slug}/`,
123
130
  categoryUrl: (pk: number) => `/products/${pk}/category_nodes/?limit=1`,
124
131
  breadcrumbUrl: (menuitemmodel: string) =>
125
132
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`
@@ -127,28 +134,38 @@ export const product = {
127
134
 
128
135
  export const wishlist = {
129
136
  getFavorites: ({ page, limit }: { page?: number; limit?: number }) =>
130
- `/wishlists/favourite-products?limit=${limit || 12}&page=${page || 1}`,
131
- addFavorite: '/wishlists/favourite-products',
132
- removeFavorite: (favPk: number) => `/wishlists/favourite-products/${favPk}`,
133
- addStockAlert: '/wishlists/product-alerts'
137
+ `/wishlists/favourite-products/?limit=${limit || 12}&page=${page || 1}`,
138
+ addFavorite: '/wishlists/favourite-products/',
139
+ removeFavorite: (favPk: number) => `/wishlists/favourite-products/${favPk}/`,
140
+ addStockAlert: '/wishlists/product-alerts/'
134
141
  };
135
142
 
136
143
  export const user = {
137
- currentUser: '/current_user',
138
- login: '/users/login',
139
- // eslint-disable-next-line projectzero/client-url
144
+ currentUser: '/current_user/',
145
+ login: '/users/login/',
140
146
  register: '/users/registration/',
141
- logout: '/users/logout',
142
- captcha: '/users/pz-captcha',
143
- profiles: '/users/profile',
144
- forgotPassword: '/users/password/reset',
145
- changeEmailVerification: (token: string) => `/users/email-set-primary/${token}`,
146
- confirmEmailVerification: (token: string) => `/users/registration/account-confirm-email/${token}`,
147
- csrfToken: '/csrf_token'
147
+ logout: '/users/logout/',
148
+ captcha: '/users/pz-captcha/',
149
+ profiles: '/users/profile/',
150
+ forgotPassword: '/users/password/reset/',
151
+ changeEmailVerification: (token: string) =>
152
+ `/users/email-set-primary/${token}/`,
153
+ confirmEmailVerification: (token: string) =>
154
+ `/users/registration/account-confirm-email/${token}/`,
155
+ csrfToken: '/csrf_token/'
156
+ };
157
+
158
+ export const b2b = {
159
+ basket: '/b2b/basket/',
160
+ saveBasket: '/b2b/basket/save/',
161
+ draftBaskets: '/b2b/basket/drafts/',
162
+ divisions: '/b2b/my-divisions/',
163
+ myQuotations: '/b2b/my-quotations/',
164
+ loadBasket: (id) => `/b2b/basket/${id}/load/`
148
165
  };
149
166
 
150
167
  export const widgets = {
151
- getWidgets: (slug: string) => `/widgets/${slug}`
168
+ getWidget: (slug: string) => `/widgets/${slug}/`
152
169
  };
153
170
 
154
171
  const URLS = {
@@ -166,6 +183,7 @@ const URLS = {
166
183
  };
167
184
 
168
185
  const UrlProxyHandler = {
186
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
169
187
  get(target, prop, receiver) {
170
188
  if (typeof target[prop] === 'function') {
171
189
  return new Proxy(target[prop], {
package/lib/cache.ts CHANGED
@@ -4,19 +4,48 @@ import { CacheOptions } from '../types';
4
4
  import logger from '../utils/log';
5
5
  import { ServerVariables } from '../utils/server-variables';
6
6
 
7
+ const hashCacheKey = (object?: Record<string, string>) => {
8
+ if (!object) {
9
+ return '';
10
+ }
11
+ const sortedFilters = Object.entries(object).sort(([firstKey], [secondKey]) =>
12
+ firstKey.localeCompare(secondKey)
13
+ );
14
+
15
+ const cacheKey = Buffer.from(
16
+ JSON.stringify(Object.fromEntries(sortedFilters))
17
+ ).toString('base64');
18
+
19
+ return `_${encodeURIComponent(cacheKey)}`;
20
+ };
7
21
  export const CacheKey = {
8
- List: (searchParams: URLSearchParams) =>
9
- `list_${encodeURIComponent(JSON.stringify(searchParams))}`,
10
- Category: (pk: number, searchParams?: URLSearchParams) =>
11
- `category_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
22
+ List: (searchParams: URLSearchParams, headers?: Record<string, string>) =>
23
+ `list_${encodeURIComponent(JSON.stringify(searchParams))}${hashCacheKey(
24
+ headers
25
+ )}`,
26
+ Category: (
27
+ pk: number,
28
+ searchParams?: URLSearchParams,
29
+ headers?: Record<string, string>
30
+ ) =>
31
+ `category_${pk}_${encodeURIComponent(
32
+ JSON.stringify(searchParams)
33
+ )}${hashCacheKey(headers)}`,
12
34
  CategorySlug: (slug: string) => `category_${slug}`,
13
- SpecialPage: (pk: number, searchParams: URLSearchParams) =>
14
- `special_page_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
35
+ SpecialPage: (
36
+ pk: number,
37
+ searchParams: URLSearchParams,
38
+ headers?: Record<string, string>
39
+ ) =>
40
+ `special_page_${pk}_${encodeURIComponent(
41
+ JSON.stringify(searchParams)
42
+ )}${hashCacheKey(headers)}`,
15
43
  Product: (pk: number, searchParams: URLSearchParams) =>
16
44
  `product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
17
45
  GroupProduct: (pk: number, searchParams: URLSearchParams) =>
18
46
  `group_product_${pk}_${encodeURIComponent(JSON.stringify(searchParams))}`,
19
47
  FlatPage: (pk: number) => `flat_page_${pk}`,
48
+ LandingPage: (pk: number) => `landing_page_${pk}`,
20
49
  Widget: (slug: string) => `widget_${slug}`,
21
50
  PrettyUrl: (pathname: string) => `pretty_url_${pathname}`,
22
51
  Menu: (depth: number, parent?: string) =>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/next",
3
3
  "description": "Core package for Project Zero Next",
4
- "version": "1.8.0",
4
+ "version": "1.9.0",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -14,13 +14,13 @@
14
14
  "pz-postdev": "bin/pz-postdev.js"
15
15
  },
16
16
  "dependencies": {
17
- "@reduxjs/toolkit": "1.9.2",
17
+ "@reduxjs/toolkit": "1.9.7",
18
18
  "cross-spawn": "7.0.3",
19
- "react-redux": "8.0.5",
19
+ "react-redux": "8.1.3",
20
20
  "react-string-replace": "1.1.1",
21
21
  "redis": "4.5.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/react-redux": "7.1.23"
24
+ "@types/react-redux": "7.1.30"
25
25
  }
26
26
  }
@@ -179,13 +179,19 @@ export const contextListMiddleware: Middleware = ({
179
179
  : `/${currentLocale}${redirectUrl}`;
180
180
  }
181
181
 
182
+ const urlObj = new URL(url, window.location.origin);
183
+ urlObj.searchParams.set('t', new Date().getTime().toString());
184
+
182
185
  if (
183
- isMobileApp ||
184
- /iPad|iPhone|iPod|Android/i.test(navigator.userAgent)
186
+ (isMobileApp ||
187
+ /iPad|iPhone|iPod|Android/i.test(navigator.userAgent)) &&
188
+ !settings.checkout?.iframeExcludedPaymentOptions?.includes(
189
+ result.payload?.pre_order?.payment_option?.slug
190
+ )
185
191
  ) {
186
- showMobile3dIframe(url);
192
+ showMobile3dIframe(urlObj.toString());
187
193
  } else {
188
- window.location.href = url;
194
+ window.location.href = urlObj.toString();
189
195
  }
190
196
 
191
197
  return;
@@ -0,0 +1,61 @@
1
+ import { Product } from './product';
2
+
3
+ export type Division = {
4
+ id: number;
5
+ name: string;
6
+ division_type: string;
7
+ parent: number;
8
+ erp_code: string;
9
+ country_id: number;
10
+ city_id: number;
11
+ township_id: number;
12
+ district_id: number;
13
+ address: string;
14
+ phone_number: string;
15
+ fax_number: string;
16
+ is_active: true;
17
+ latitude: null;
18
+ longitude: null;
19
+ monthly_order_limit: string;
20
+ current_balance: string;
21
+ current_balance_last_update: null | string;
22
+ extra_data: object;
23
+ };
24
+
25
+ interface ProductB2b extends Product {
26
+ asorti: string;
27
+ category: string;
28
+ currency: string;
29
+ variants: {
30
+ [key: string]: any;
31
+ };
32
+ product_image: string;
33
+ }
34
+
35
+ export type BasketResponse = {
36
+ id: number;
37
+ total_amount: string;
38
+ total_quantity: number;
39
+ basket_items: [
40
+ {
41
+ total_amount: string;
42
+ price: string;
43
+ quantity: number;
44
+ divisions: Division[];
45
+ product: ProductB2b;
46
+ }
47
+ ];
48
+ };
49
+
50
+ export type BasketParams = {
51
+ division: string;
52
+ product_remote_id: string;
53
+ quantity: string;
54
+ };
55
+
56
+ export interface GetResponse<T> {
57
+ count: number;
58
+ next: null;
59
+ previous: null;
60
+ results: T[];
61
+ }
@@ -8,3 +8,4 @@ export * from './product';
8
8
  export * from './widget';
9
9
  export * from './flatpage';
10
10
  export * from './order';
11
+ export * from './b2b';
@@ -0,0 +1,7 @@
1
+ export interface LandingPage {
2
+ landing_page: {
3
+ url: string;
4
+ template: string;
5
+ is_active: boolean;
6
+ };
7
+ }
@@ -106,3 +106,14 @@ export interface Order {
106
106
  tracking_number: string;
107
107
  tracking_url: string;
108
108
  }
109
+
110
+ export interface Quotations {
111
+ id: number;
112
+ name: string;
113
+ net_amount: string;
114
+ number: string;
115
+ status: string;
116
+ total_amount: string;
117
+ modified_at: string;
118
+ created_at: string;
119
+ }
package/types/index.ts CHANGED
@@ -168,6 +168,12 @@ export interface Settings {
168
168
  source: string;
169
169
  destination: string;
170
170
  }>;
171
+ checkout?: {
172
+ /**
173
+ * If there is a need to exclude certain payment options from the iframe, for instance, due to CORS issues, this option can be utilized by passing the slugs associated with the desired payment options.
174
+ */
175
+ iframeExcludedPaymentOptions?: string[];
176
+ };
171
177
  }
172
178
 
173
179
  export interface CacheOptions {
@@ -58,7 +58,9 @@ const appFetch = async <T>(
58
58
 
59
59
  logger.trace(`FETCH RESPONSE`, { url, response, ip });
60
60
  } catch (error) {
61
- logger.error(`FETCH FAILED`, { url, status, error, ip });
61
+ if (!url.toString().includes('/cms/seo/')) {
62
+ logger.error(`FETCH FAILED`, { url, status, error, ip });
63
+ }
62
64
  }
63
65
 
64
66
  return response;