@akinon/next 1.0.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.
Files changed (98) hide show
  1. package/.prettierrc +13 -0
  2. package/api/auth.ts +217 -0
  3. package/api/cache.ts +44 -0
  4. package/api/client.ts +157 -0
  5. package/api/logout.ts +42 -0
  6. package/assets/styles/index.scss +24 -0
  7. package/bin/pz-install-plugins.js +33 -0
  8. package/components/client-root.tsx +69 -0
  9. package/components/image.tsx +133 -0
  10. package/components/mobile-app-toggler.tsx +15 -0
  11. package/components/oauth-login.tsx +24 -0
  12. package/components/plugin-module.tsx +78 -0
  13. package/components/pz-providers.tsx +24 -0
  14. package/components/pz-root.tsx +21 -0
  15. package/components/redirect-three-d/content/index.tsx +64 -0
  16. package/components/redirect-three-d/index.tsx +17 -0
  17. package/components/selected-payment-option-view.tsx +66 -0
  18. package/components/trans.tsx +39 -0
  19. package/data/client/account.ts +188 -0
  20. package/data/client/address.ts +107 -0
  21. package/data/client/api.ts +42 -0
  22. package/data/client/basket.ts +85 -0
  23. package/data/client/checkout.ts +477 -0
  24. package/data/client/misc.ts +101 -0
  25. package/data/client/product.ts +90 -0
  26. package/data/client/user.ts +83 -0
  27. package/data/client/wishlist.ts +79 -0
  28. package/data/server/category.ts +121 -0
  29. package/data/server/flatpage.ts +21 -0
  30. package/data/server/index.ts +8 -0
  31. package/data/server/list.ts +56 -0
  32. package/data/server/menu.ts +35 -0
  33. package/data/server/product.ts +86 -0
  34. package/data/server/seo.ts +48 -0
  35. package/data/server/special-page.ts +42 -0
  36. package/data/server/widget.ts +27 -0
  37. package/data/urls.ts +184 -0
  38. package/hocs/client/index.ts +1 -0
  39. package/hocs/client/with-segment-defaults.tsx +26 -0
  40. package/hocs/server/index.ts +1 -0
  41. package/hocs/server/with-segment-defaults.tsx +83 -0
  42. package/hooks/index.ts +8 -0
  43. package/hooks/use-captcha.tsx +76 -0
  44. package/hooks/use-common-product-attributes.ts +36 -0
  45. package/hooks/use-debounce.ts +20 -0
  46. package/hooks/use-localization.ts +63 -0
  47. package/hooks/use-media-query.ts +36 -0
  48. package/hooks/use-on-click-outside.tsx +28 -0
  49. package/hooks/use-router.ts +45 -0
  50. package/hooks/use-translation.ts +14 -0
  51. package/lib/cache.ts +185 -0
  52. package/localization/index.ts +5 -0
  53. package/localization/provider.tsx +58 -0
  54. package/middlewares/currency.ts +55 -0
  55. package/middlewares/default.ts +224 -0
  56. package/middlewares/index.ts +29 -0
  57. package/middlewares/locale.ts +61 -0
  58. package/middlewares/oauth-login.ts +78 -0
  59. package/middlewares/pretty-url.ts +94 -0
  60. package/middlewares/redirection-payment.ts +117 -0
  61. package/middlewares/three-d-redirection.ts +122 -0
  62. package/middlewares/url-redirection.ts +61 -0
  63. package/package.json +20 -0
  64. package/plugins.js +7 -0
  65. package/redux/hooks.ts +7 -0
  66. package/redux/middlewares/checkout.ts +231 -0
  67. package/redux/middlewares/index.ts +50 -0
  68. package/redux/reducers/checkout.ts +164 -0
  69. package/redux/reducers/config.ts +28 -0
  70. package/redux/reducers/header.ts +59 -0
  71. package/redux/reducers/index.ts +15 -0
  72. package/redux/reducers/root.ts +61 -0
  73. package/tailwind/rtl.js +137 -0
  74. package/types/commerce/account.ts +68 -0
  75. package/types/commerce/address.ts +94 -0
  76. package/types/commerce/basket.ts +43 -0
  77. package/types/commerce/category.ts +114 -0
  78. package/types/commerce/checkout.ts +132 -0
  79. package/types/commerce/flatpage.ts +7 -0
  80. package/types/commerce/index.ts +10 -0
  81. package/types/commerce/misc.ts +127 -0
  82. package/types/commerce/order.ts +108 -0
  83. package/types/commerce/product.ts +110 -0
  84. package/types/commerce/widget.ts +28 -0
  85. package/types/gtm.ts +16 -0
  86. package/types/index.ts +207 -0
  87. package/types/next-auth.d.ts +24 -0
  88. package/utils/app-fetch.ts +62 -0
  89. package/utils/generate-commerce-search-params.ts +22 -0
  90. package/utils/get-currency.ts +29 -0
  91. package/utils/image-loader.ts +31 -0
  92. package/utils/index.ts +132 -0
  93. package/utils/localization.ts +29 -0
  94. package/utils/log.ts +138 -0
  95. package/utils/menu-generator.ts +27 -0
  96. package/utils/mobile-3d-iframe.ts +58 -0
  97. package/utils/server-translation.ts +57 -0
  98. package/utils/server-variables.ts +9 -0
@@ -0,0 +1,78 @@
1
+ import {
2
+ NextFetchEvent,
3
+ NextMiddleware,
4
+ NextRequest,
5
+ NextResponse
6
+ } from 'next/server';
7
+ import Settings from 'settings';
8
+ import { getUrlPathWithLocale } from '../utils/localization';
9
+
10
+ const withOauthLogin =
11
+ (middleware: NextMiddleware) =>
12
+ async (req: NextRequest, event: NextFetchEvent) => {
13
+ const url = req.nextUrl.clone();
14
+ const loginUrlMatcherRegex = new RegExp(/^\/(\w+)\/login\/?$/);
15
+ const loginCallbackUrlMatcherRegex = new RegExp(
16
+ /^\/(\w+)\/login\/callback\/?$/
17
+ );
18
+
19
+ const headers = {
20
+ 'x-forwarded-host':
21
+ req.headers.get('x-forwarded-host') || req.headers.get('host') || ''
22
+ };
23
+
24
+ if (loginUrlMatcherRegex.test(url.pathname)) {
25
+ const provider = url.pathname.match(loginUrlMatcherRegex)[1];
26
+ const response = NextResponse.rewrite(
27
+ `${Settings.commerceUrl}/${provider}/login/`,
28
+ {
29
+ headers
30
+ }
31
+ );
32
+
33
+ if (req.headers.get('referer')) {
34
+ response.cookies.set(
35
+ 'pz-oauth-callback-url',
36
+ req.headers.get('referer')
37
+ );
38
+ }
39
+
40
+ return response;
41
+ }
42
+
43
+ if (loginCallbackUrlMatcherRegex.test(url.pathname)) {
44
+ const provider = url.pathname.match(loginCallbackUrlMatcherRegex)[1];
45
+
46
+ return NextResponse.rewrite(
47
+ `${Settings.commerceUrl}/${provider}/login/callback/${url.search}`,
48
+ {
49
+ headers
50
+ }
51
+ );
52
+ }
53
+
54
+ if (!url.pathname.startsWith('/baskets/basket')) {
55
+ return middleware(req, event);
56
+ }
57
+
58
+ if (req.cookies.get('messages')?.value.includes('Successfully signed in')) {
59
+ let redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
60
+ '/auth/oauth-login',
61
+ req.cookies.get('pz-locale')?.value
62
+ )}`;
63
+ let callbackUrl = req.cookies.get('pz-oauth-callback-url')?.value ?? '';
64
+
65
+ if (callbackUrl.length) {
66
+ redirectUrlWithLocale += `?next=${encodeURIComponent(callbackUrl)}`;
67
+ }
68
+
69
+ const response = NextResponse.redirect(redirectUrlWithLocale);
70
+ response.cookies.delete('messages');
71
+ response.cookies.delete('pz-oauth-callback-url');
72
+ return response;
73
+ }
74
+
75
+ return middleware(req, event);
76
+ };
77
+
78
+ export default withOauthLogin;
@@ -0,0 +1,94 @@
1
+ import { Cache, CacheKey } from '../lib/cache';
2
+ import { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
3
+ import { ROUTES } from 'routes';
4
+ import { URLS } from '../data/urls';
5
+ import Settings from 'settings';
6
+ import { urlLocaleMatcherRegex } from '../utils';
7
+ import { PzNextRequest } from '.';
8
+ import logger from '../utils/log';
9
+
10
+ type PrettyUrlResult = {
11
+ matched: boolean;
12
+ path?: string;
13
+ };
14
+
15
+ const resolvePrettyUrlHandler = (pathname: string) => async () => {
16
+ let results = <{ old_path: string }[]>[];
17
+ let prettyUrlResult: PrettyUrlResult = {
18
+ matched: false
19
+ };
20
+
21
+ try {
22
+ const requestUrl = URLS.misc.prettyUrls(pathname);
23
+
24
+ logger.debug(`Resolving pretty url`, { pathname, requestUrl });
25
+
26
+ const apiResponse = await fetch(requestUrl);
27
+ const data = await apiResponse.json();
28
+ ({ results } = data);
29
+
30
+ const matched = results.length > 0;
31
+ const [{ old_path: path } = { old_path: '' }] = results;
32
+ prettyUrlResult = {
33
+ matched,
34
+ path
35
+ };
36
+
37
+ logger.trace('Pretty url result', prettyUrlResult);
38
+ } catch (error) {
39
+ logger.error('Error resolving pretty url', { error, pathname });
40
+ }
41
+
42
+ return prettyUrlResult;
43
+ };
44
+
45
+ const resolvePrettyUrl = async (pathname: string) => {
46
+ return Cache.wrap(
47
+ CacheKey.PrettyUrl(pathname),
48
+ resolvePrettyUrlHandler(pathname),
49
+ {
50
+ useProxy: true
51
+ }
52
+ );
53
+ };
54
+
55
+ const withPrettyUrl =
56
+ (middleware: NextMiddleware) =>
57
+ async (req: PzNextRequest, event: NextFetchEvent) => {
58
+ const url = req.nextUrl.clone();
59
+ const matchedLanguagePrefix = url.pathname.match(
60
+ urlLocaleMatcherRegex
61
+ )?.[0];
62
+ const prettyUrlPathname = url.pathname.replace(matchedLanguagePrefix, '');
63
+ const isValidPrettyUrlPath = (pathname: string) => {
64
+ return (
65
+ new RegExp(/^\/[a-zA-Z0-9/]+(?:-[a-zA-Z0-9/]+)*$/).test(pathname) &&
66
+ !Object.entries(ROUTES).find(([, value]) =>
67
+ new RegExp(`^${value}$`).test(pathname)
68
+ )
69
+ );
70
+ };
71
+
72
+ if (
73
+ !isValidPrettyUrlPath(prettyUrlPathname) ||
74
+ Settings.commerceUrl === 'default'
75
+ ) {
76
+ return middleware(req, event);
77
+ }
78
+
79
+ const prettyUrlResult = await resolvePrettyUrl(prettyUrlPathname);
80
+
81
+ if (prettyUrlResult.matched) {
82
+ req.middlewareParams.rewrites.prettyUrl = prettyUrlResult.path;
83
+ logger.debug('Resolved pretty url', {
84
+ source: prettyUrlPathname,
85
+ dest: prettyUrlResult.path
86
+ });
87
+
88
+ return middleware(req, event);
89
+ }
90
+
91
+ return middleware(req, event);
92
+ };
93
+
94
+ export default withPrettyUrl;
@@ -0,0 +1,117 @@
1
+ import { Buffer } from 'buffer';
2
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
3
+ import Settings from 'settings';
4
+ import logger from '../utils/log';
5
+ import { PzNextRequest } from '.';
6
+ import { getUrlPathWithLocale } from '../utils/localization';
7
+ import { urlLocaleMatcherRegex } from '../utils';
8
+
9
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
10
+ if (stream) {
11
+ const chunks = [];
12
+ let result = '';
13
+
14
+ try {
15
+ for await (const chunk of stream as any) {
16
+ chunks.push(Buffer.from(chunk));
17
+ }
18
+
19
+ result = Buffer.concat(chunks).toString('utf-8');
20
+ } catch (error) {
21
+ logger.error('Error while reading body stream', {
22
+ middleware: 'redirection-payment',
23
+ error
24
+ });
25
+ }
26
+
27
+ return result;
28
+ }
29
+ return null;
30
+ };
31
+
32
+ const withRedirectionPayment =
33
+ (middleware: NextMiddleware) =>
34
+ async (req: PzNextRequest, event: NextFetchEvent) => {
35
+ const url = req.nextUrl.clone();
36
+ const pathnameWithoutLocale = url.pathname.replace(
37
+ urlLocaleMatcherRegex,
38
+ ''
39
+ );
40
+ const searchParams = new URLSearchParams(url.search);
41
+
42
+ if (
43
+ !pathnameWithoutLocale.startsWith('/orders') ||
44
+ searchParams.get('page') !== 'RedirectionPageCompletePage' ||
45
+ req.method !== 'POST'
46
+ ) {
47
+ return middleware(req, event);
48
+ }
49
+
50
+ const body = await streamToString(req.body);
51
+ const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
52
+
53
+ const request = await fetch(requestUrl, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'X-Requested-With': 'XMLHttpRequest',
57
+ 'Content-Type': 'application/x-www-form-urlencoded',
58
+ Cookie: req.headers.get('cookie') ?? ''
59
+ },
60
+ body
61
+ });
62
+
63
+ logger.info('Complete redirection payment request', {
64
+ requestUrl,
65
+ status: request.status
66
+ });
67
+
68
+ const response = await request.json();
69
+
70
+ const { context_list: contextList } = response;
71
+ const redirectionContext = contextList?.find(
72
+ (context) => context.page_context?.redirect_url
73
+ );
74
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
75
+
76
+ if (!redirectUrl) {
77
+ logger.info('Order success page context list', {
78
+ middleware: 'redirection-payment',
79
+ contextList
80
+ });
81
+
82
+ logger.warn(
83
+ 'No redirection url for order success page found in page_context',
84
+ { middleware: 'redirection-payment' }
85
+ );
86
+ return NextResponse.redirect(
87
+ `${url.origin}${getUrlPathWithLocale(
88
+ `/orders/checkout?${searchParams.toString()}`,
89
+ req.cookies.get('pz-locale')?.value
90
+ )}`,
91
+ 303
92
+ );
93
+ }
94
+
95
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
96
+ redirectUrl,
97
+ req.cookies.get('pz-locale')?.value
98
+ )}`;
99
+
100
+ logger.info('Redirecting to order success page', {
101
+ middleware: 'redirection-payment',
102
+ redirectUrlWithLocale
103
+ });
104
+
105
+ // Using POST method while redirecting causes an error,
106
+ // So we use 303 status code to change the method to GET
107
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
108
+
109
+ nextResponse.headers.set(
110
+ 'Set-Cookie',
111
+ request.headers.get('set-cookie') ?? ''
112
+ );
113
+
114
+ return nextResponse;
115
+ };
116
+
117
+ export default withRedirectionPayment;
@@ -0,0 +1,122 @@
1
+ import { NextFetchEvent, NextMiddleware, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+ import { Buffer } from 'buffer';
4
+ import logger from '../utils/log';
5
+ import { getUrlPathWithLocale } from '../utils/localization';
6
+ import { PzNextRequest } from '.';
7
+
8
+ const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
9
+ if (stream) {
10
+ const chunks = [];
11
+ let result = '';
12
+
13
+ try {
14
+ for await (const chunk of stream as any) {
15
+ chunks.push(Buffer.from(chunk));
16
+ }
17
+
18
+ result = Buffer.concat(chunks).toString('utf-8');
19
+ } catch (error) {
20
+ logger.error('Error while reading body stream', {
21
+ middleware: 'three-d-redirection',
22
+ error
23
+ });
24
+ }
25
+
26
+ return result;
27
+ }
28
+ return null;
29
+ };
30
+
31
+ const withThreeDRedirection =
32
+ (middleware: NextMiddleware) =>
33
+ async (req: PzNextRequest, event: NextFetchEvent) => {
34
+ const url = req.nextUrl.clone();
35
+
36
+ if (url.search.indexOf('CreditCardThreeDSecurePage') === -1) {
37
+ return middleware(req, event);
38
+ }
39
+
40
+ const body = await streamToString(req.body);
41
+ const params = body.split('&');
42
+ const encodedPzParams = decodeURIComponent(
43
+ params.find((param) => param.startsWith('pzparams'))?.split('=')[1] ?? ''
44
+ );
45
+
46
+ logger.debug('Encoded 3D form extra params', encodedPzParams);
47
+
48
+ if (!encodedPzParams.length) {
49
+ logger.warn('No pzparams found in 3D form params');
50
+ return middleware(req, event);
51
+ }
52
+
53
+ const pzParams = JSON.parse(decodeURIComponent(encodedPzParams)) as {
54
+ session: string;
55
+ locale: string;
56
+ };
57
+
58
+ logger.debug('3D form extra params', pzParams);
59
+
60
+ const requestUrl = `${Settings.commerceUrl}/orders/checkout/${url.search}`;
61
+
62
+ const request = await fetch(requestUrl, {
63
+ method: 'POST',
64
+ headers: {
65
+ 'X-Requested-With': 'XMLHttpRequest',
66
+ 'Content-Type': 'application/x-www-form-urlencoded',
67
+ Cookie: `osessionid=${pzParams.session}`
68
+ },
69
+ body
70
+ });
71
+
72
+ logger.info('Complete 3D payment request', {
73
+ requestUrl,
74
+ status: request.status
75
+ });
76
+
77
+ const response = await request.json();
78
+
79
+ const { context_list: contextList } = response;
80
+ const redirectionContext = contextList?.find(
81
+ (context) => context.page_context?.redirect_url
82
+ );
83
+ const redirectUrl = redirectionContext?.page_context?.redirect_url;
84
+
85
+ logger.info('Order success page context list', {
86
+ middleware: 'three-d-redirection',
87
+ contextList
88
+ });
89
+
90
+ if (!redirectUrl) {
91
+ logger.warn(
92
+ 'No redirection url for order success page found in page_context',
93
+ {
94
+ middleware: 'three-d-redirection'
95
+ }
96
+ );
97
+ return middleware(req, event);
98
+ }
99
+
100
+ const redirectUrlWithLocale = `${url.origin}${getUrlPathWithLocale(
101
+ redirectUrl,
102
+ req.cookies.get('pz-locale')?.value
103
+ )}`;
104
+
105
+ logger.info('Redirecting to order success page', {
106
+ middleware: 'three-d-redirection',
107
+ redirectUrlWithLocale
108
+ });
109
+
110
+ // Using POST method while redirecting causes an error,
111
+ // So we use 303 status code to change the method to GET
112
+ const nextResponse = NextResponse.redirect(redirectUrlWithLocale, 303);
113
+
114
+ nextResponse.headers.set(
115
+ 'Set-Cookie',
116
+ request.headers.get('set-cookie') ?? ''
117
+ );
118
+
119
+ return nextResponse;
120
+ };
121
+
122
+ export default withThreeDRedirection;
@@ -0,0 +1,61 @@
1
+ import { NextFetchEvent, NextMiddleware } from 'next/server';
2
+ import settings from 'settings';
3
+ import { PzNextRequest } from '.';
4
+ import logger from '../utils/log';
5
+ import { urlLocaleMatcherRegex } from '../utils';
6
+ import { getUrlPathWithLocale } from '../utils/localization';
7
+ import { ROUTES } from 'routes';
8
+
9
+ // This middleware is used to handle url redirections set in Omnitron
10
+
11
+ const withUrlRedirection =
12
+ (middleware: NextMiddleware) =>
13
+ async (req: PzNextRequest, event: NextFetchEvent) => {
14
+ const url = req.nextUrl.clone();
15
+ const pathnameWithoutLocale = url.pathname.replace(
16
+ urlLocaleMatcherRegex,
17
+ ''
18
+ );
19
+
20
+ if (
21
+ Object.entries(ROUTES).find(([, value]) =>
22
+ new RegExp(`^${value}$`).test(pathnameWithoutLocale)
23
+ ) ||
24
+ pathnameWithoutLocale.startsWith('/orders')
25
+ ) {
26
+ return middleware(req, event);
27
+ }
28
+
29
+ try {
30
+ const request = await fetch(
31
+ `${settings.commerceUrl}${pathnameWithoutLocale}${url.search}`,
32
+ {
33
+ redirect: 'manual',
34
+ next: {
35
+ revalidate: 0
36
+ }
37
+ }
38
+ );
39
+
40
+ if (request.headers.get('location')) {
41
+ const location = request.headers.get('location');
42
+ const redirectUrl = new URL(
43
+ request.headers.get('location'),
44
+ location.startsWith('http') ? '' : process.env.NEXT_PUBLIC_URL
45
+ );
46
+
47
+ redirectUrl.pathname = getUrlPathWithLocale(
48
+ redirectUrl.pathname,
49
+ req.middlewareParams.rewrites.locale
50
+ );
51
+
52
+ return Response.redirect(redirectUrl.toString(), request.status);
53
+ }
54
+ } catch (error) {
55
+ logger.error('withUrlRedirection error', error);
56
+ }
57
+
58
+ return middleware(req, event);
59
+ };
60
+
61
+ export default withUrlRedirection;
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@akinon/next",
3
+ "description": "Core package for Project Zero Next",
4
+ "version": "1.0.0",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "bin": {
8
+ "pz-install-plugins": "bin/pz-install-plugins.js"
9
+ },
10
+ "dependencies": {
11
+ "@reduxjs/toolkit": "1.9.2",
12
+ "cross-spawn": "7.0.3",
13
+ "react-redux": "8.0.5",
14
+ "react-string-replace": "1.1.1",
15
+ "redis": "4.5.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react-redux": "7.1.23"
19
+ }
20
+ }
package/plugins.js ADDED
@@ -0,0 +1,7 @@
1
+ module.exports = [
2
+ 'pz-basket-gift-pack',
3
+ 'pz-click-collect',
4
+ 'pz-one-click-checkout',
5
+ 'pz-pay-on-delivery',
6
+ 'pz-checkout-gift-pack',
7
+ ];
package/redux/hooks.ts ADDED
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+
3
+ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
4
+ import type { RootState, TypedDispatch } from 'redux/store';
5
+
6
+ export const useAppDispatch = () => useDispatch<TypedDispatch>();
7
+ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;