@akinon/next 1.8.0 → 1.10.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,25 @@
1
1
  # @akinon/next
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ZERO-2330: Fix logout endpoint
8
+ - ZERO-2327: Add service for bulk cancellation
9
+
10
+ ## 1.9.0
11
+
12
+ ### Minor Changes
13
+
14
+ - ZERO-2297: Add landing page structure
15
+ - ZERO-2317: Prevent browser cache using timestamp
16
+ - ZERO-2293: Upgrade Redux
17
+ - ZERO-2285: Allow custom headers in server data fetch functions
18
+ - ZERO-1577: Add option to exclude payment options from iframe
19
+ - ZERO-2282: Allow custom headers in client proxy API
20
+ - ZERO-2308: Add B2B endpoints and types
21
+ - ZERO-2202: Ensure commerce endpoints end with trailing slash
22
+
3
23
  ## 1.8.0
4
24
 
5
25
  ### 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, {
@@ -121,6 +130,15 @@ const accountApi = api.injectEndpoints({
121
130
  body
122
131
  })
123
132
  }),
133
+ bulkCancellation: builder.mutation<void, AccountOrderCancellation>({
134
+ query: (body) => ({
135
+ url: buildClientRequestUrl(account.bulkCancellationRequest, {
136
+ contentType: 'application/json'
137
+ }),
138
+ method: 'POST',
139
+ body
140
+ })
141
+ }),
124
142
  getCancellationReasons: builder.query<AccountOrderCancellationReason, void>(
125
143
  {
126
144
  query: () => buildClientRequestUrl(account.cancellationReasons)
@@ -170,12 +188,14 @@ export const {
170
188
  useGetContactSubjectsQuery,
171
189
  useGetOrderQuery,
172
190
  useGetOrdersQuery,
191
+ useGetQuotationsQuery,
173
192
  useSendContactMutation,
174
193
  useUpdateEmailMutation,
175
194
  useUpdatePasswordMutation,
176
195
  useUpdateProfileMutation,
177
196
  useGetProfileInfoQuery,
178
197
  useCancelOrderMutation,
198
+ useBulkCancellationMutation,
179
199
  useGetCancellationReasonsQuery,
180
200
  useGetBasketOffersQuery,
181
201
  useGetFutureBasketOffersQuery,
@@ -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,18 @@
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
+ bulkCancellationRequest: '/users/orders/bulk_cancellation_requests/',
11
+ cancellationReasons: '/users/orders/cancellation_reasons/',
12
+ updatePassword: '/users/password/change/',
13
+ updateEmail: '/users/email-change/',
14
+ updateProfile: '/users/profile/',
15
+ getContactSubjects: '/users/contact-us-subjects/',
14
16
  getOrders: ({
15
17
  page,
16
18
  limit,
@@ -20,9 +22,10 @@ export const account = {
20
22
  limit?: number;
21
23
  createdDate?: string;
22
24
  }) =>
23
- `/users/orders?page=${page || 1}&limit=${limit || 12} ${
25
+ `/users/orders/?page=${page || 1}&limit=${limit || 12} ${
24
26
  createdDate ? `&created_date__gte=${createdDate}` : ''
25
27
  }`,
28
+ getQuotations: '/b2b/my-quotations',
26
29
  sendContact: '/users/contact-us',
27
30
  passwordReset: (slug: string) => `/users/reset/${slug}`,
28
31
  anonymize: '/users/anonymize/',
@@ -31,95 +34,100 @@ export const account = {
31
34
  };
32
35
 
33
36
  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',
37
+ base: '/address/',
38
+ countries: '/address/country/',
39
+ getAddresses: '/address/detailed/',
40
+ getCities: (country: string) => `/address/city/?country=${country}`,
41
+ getTownships: (city: string) => `/address/township/?city=${city}`,
42
+ getDistricts: (township: string) => `/address/district/?township=${township}`,
43
+ getRetailStore: '/address/city/?retailstore__isnull=false',
41
44
  getRetailStoreCities: (country: string) =>
42
- `/address/city?retailstore__isnull=false&country=${country}`,
45
+ `/address/city/?retailstore__isnull=false&country=${country}`,
43
46
  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}`
47
+ `/address/township/?retailstore__isnull=false&city=${city}`,
48
+ editAddress: (pk: number) => `/address/${pk}/`,
49
+ removeAddress: (id: number) => `/address/${id}/`,
50
+ setDefaultAddress: (pk: number) => `/address/${pk}/`
48
51
  };
49
52
 
50
53
  export const basket = {
51
- getBasket: '/baskets/basket'
54
+ getBasket: '/baskets/basket/'
52
55
  };
53
56
 
54
57
  export const category = {
55
- // eslint-disable-next-line projectzero/client-url
56
58
  list: '/list/',
57
59
  getCategoryByPk: (pk: number) => `/category/${pk}/`,
58
- getCategoryBySlug: (slug: string) => `/${slug}`,
60
+ getCategoryBySlug: (slug: string) => `/${slug}/`,
59
61
  getSpecialPageByPk: (pk: number) => `/special-page/${pk}/`
60
62
  };
61
63
 
62
64
  export const checkout = {
63
- get3dRedirectForm: '/orders/redirect-three-d',
64
- fetchCheckout: '/orders/checkout',
65
+ get3dRedirectForm: '/orders/redirect-three-d/',
66
+ fetchCheckout: '/orders/checkout/',
65
67
  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',
68
+ `/orders/completed/${token}/?format=json`,
69
+ getContract: (slug: string) => `/orders/contract/?contract_name=${slug}`,
70
+ completeCreditCardPayment:
71
+ '/orders/checkout/?page=CreditCardConfirmationPage',
72
+ completeFundsTransfer: '/orders/checkout/?page=FundsTransferPage',
73
+ guestLogin: '/orders/checkout/?page=IndexPage',
74
+ setDeliveryOption: '/orders/checkout/?page=DeliveryOptionSelectionPage',
75
+ setAddresses: '/orders/checkout/?page=AddressSelectionPage',
76
+ setShippingOption: '/orders/checkout/?page=ShippingOptionSelectionPage',
77
+ setPaymentOption: '/orders/checkout/?page=PaymentOptionSelectionPage',
78
+ setBinNumber: '/orders/checkout/?page=BinNumberPage',
79
+ setMasterpassBinNumber: '/orders/checkout/?page=MasterpassBinNumberPage',
80
+ setInstallmentOption: '/orders/checkout/?page=InstallmentSelectionPage',
78
81
  setMasterPassInstallmentOption:
79
- '/orders/checkout?page=MasterpassInstallmentPage',
80
- setFundsTransferOption: '/orders/checkout?page=FundsTransferChoicePage',
81
- getCheckoutProviders: '/orders/checkout?page=CheckoutProviderIndexPage',
82
- setCheckoutProvider: '/orders/checkout?page=CheckoutProviderSelectionPage',
82
+ '/orders/checkout/?page=MasterpassInstallmentPage',
83
+ setFundsTransferOption: '/orders/checkout/?page=FundsTransferChoicePage',
84
+ getCheckoutProviders: '/orders/checkout-provider-list/',
85
+ getCheckoutProvidersIndex: '/orders/checkout/?page=CheckoutProviderIndexPage',
86
+ setCheckoutProvider: '/orders/checkout/?page=CheckoutProviderSelectionPage',
83
87
  completeRedirectionPayment:
84
- '/orders/checkout?page=RedirectionPaymentSelectedPage',
88
+ '/orders/checkout/?page=RedirectionPaymentSelectedPage',
85
89
  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'
90
+ '/orders/checkout/?page=ConfirmationPaymentSelectedPage',
91
+ confirmationQuery: '/orders/checkout/?page=ConfirmationPaymentQueryPage',
92
+ confirmationComplete:
93
+ '/orders/checkout/?page=ConfirmationPaymentCompletePage',
94
+ completeLoyaltyPayment: '/orders/checkout/?page=LoyaltyPaymentSelectedPage',
95
+ loyaltyMoneyUsage: '/orders/checkout/?page=LoyaltyMoneyUsagePage',
96
+ setOrderNote: '/orders/checkout/?page=OrderNotePage'
92
97
  };
93
98
 
94
99
  export const flatpage = {
95
100
  getFlatPageByPk: (pk: number) => `/shop-flat-page/${pk}/`
96
101
  };
97
102
 
103
+ export const landingpage = {
104
+ getLandingPageByPk: (pk: number) => `/landing-page/${pk}/`
105
+ };
106
+
98
107
  export const misc = {
99
- autocomplete: '/autocomplete',
100
- stores: '/stores',
101
- menu: '/misc/menus/generate',
108
+ autocomplete: '/autocomplete/',
109
+ stores: '/stores/',
110
+ menu: '/misc/menus/generate/',
102
111
  prettyUrl: (pathname: string) => `/misc/pretty-url${pathname}/`,
103
112
  prettyUrls: (pathname: string) => `/pretty_urls/?new_path__exact=${pathname}`,
104
- emailSubscription: '/email-subscription',
113
+ emailSubscription: '/email-subscription/',
105
114
  menus: (depthHeight: number, parent?: string) =>
106
115
  `/menus/generate/?depth_height=${depthHeight}${
107
116
  parent ? `&parent=${parent}` : ''
108
117
  }`,
109
- // eslint-disable-next-line projectzero/client-url
110
118
  cmsSeo: (slug: string | string[]) => `/cms/seo/?url=${slug ? slug : '/'}`,
111
- setCurrency: '/users/activate-currency'
119
+ setCurrency: '/users/activate-currency/'
112
120
  };
113
121
 
114
122
  export const product = {
115
123
  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}`,
124
+ installments: (productId: number) => `/payments/cards/product/${productId}/`,
125
+ getProductByPk: (pk: number) => `/product/${pk}/`,
126
+ getGroupProductByPk: (pk: number) => `/group-product/${pk}/`,
119
127
  getRetailStoreStock: (productPk: string, queryString: string) =>
120
- `/retail_store_stock/${productPk}?${queryString}`,
121
- addProduct: '/baskets/basket',
122
- slug: (slug: string) => `/${slug}`,
128
+ `/retail_store_stock/${productPk}/?${queryString}`,
129
+ addProduct: '/baskets/basket/',
130
+ slug: (slug: string) => `/${slug}/`,
123
131
  categoryUrl: (pk: number) => `/products/${pk}/category_nodes/?limit=1`,
124
132
  breadcrumbUrl: (menuitemmodel: string) =>
125
133
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`
@@ -127,28 +135,38 @@ export const product = {
127
135
 
128
136
  export const wishlist = {
129
137
  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'
138
+ `/wishlists/favourite-products/?limit=${limit || 12}&page=${page || 1}`,
139
+ addFavorite: '/wishlists/favourite-products/',
140
+ removeFavorite: (favPk: number) => `/wishlists/favourite-products/${favPk}/`,
141
+ addStockAlert: '/wishlists/product-alerts/'
134
142
  };
135
143
 
136
144
  export const user = {
137
- currentUser: '/current_user',
138
- login: '/users/login',
139
- // eslint-disable-next-line projectzero/client-url
145
+ currentUser: '/current_user/',
146
+ login: '/users/login/',
140
147
  register: '/users/registration/',
141
148
  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'
149
+ captcha: '/users/pz-captcha/',
150
+ profiles: '/users/profile/',
151
+ forgotPassword: '/users/password/reset/',
152
+ changeEmailVerification: (token: string) =>
153
+ `/users/email-set-primary/${token}/`,
154
+ confirmEmailVerification: (token: string) =>
155
+ `/users/registration/account-confirm-email/${token}/`,
156
+ csrfToken: '/csrf_token/'
157
+ };
158
+
159
+ export const b2b = {
160
+ basket: '/b2b/basket/',
161
+ saveBasket: '/b2b/basket/save/',
162
+ draftBaskets: '/b2b/basket/drafts/',
163
+ divisions: '/b2b/my-divisions/',
164
+ myQuotations: '/b2b/my-quotations/',
165
+ loadBasket: (id) => `/b2b/basket/${id}/load/`
148
166
  };
149
167
 
150
168
  export const widgets = {
151
- getWidgets: (slug: string) => `/widgets/${slug}`
169
+ getWidget: (slug: string) => `/widgets/${slug}/`
152
170
  };
153
171
 
154
172
  const URLS = {
@@ -166,6 +184,7 @@ const URLS = {
166
184
  };
167
185
 
168
186
  const UrlProxyHandler = {
187
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
169
188
  get(target, prop, receiver) {
170
189
  if (typeof target[prop] === 'function') {
171
190
  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.10.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;