@akinon/next 2.0.0-beta.9 → 2.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 (121) hide show
  1. package/CHANGELOG.md +434 -23
  2. package/__tests__/next-config.test.ts +83 -0
  3. package/__tests__/tsconfig.json +23 -0
  4. package/api/auth.ts +367 -63
  5. package/api/barcode-search.ts +59 -0
  6. package/api/cache.ts +41 -5
  7. package/api/client.ts +21 -4
  8. package/api/form.ts +85 -0
  9. package/api/image-proxy.ts +75 -0
  10. package/api/product-categories.ts +53 -0
  11. package/api/similar-product-list.ts +63 -0
  12. package/api/similar-products.ts +111 -0
  13. package/api/virtual-try-on.ts +382 -0
  14. package/assets/styles/index.scss +84 -0
  15. package/babel.config.js +6 -0
  16. package/bin/pz-generate-routes.js +115 -0
  17. package/bin/pz-install-plugins.js +1 -1
  18. package/bin/pz-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/components/accordion.tsx +21 -6
  22. package/components/client-root.tsx +119 -3
  23. package/components/file-input.tsx +65 -3
  24. package/components/index.ts +1 -0
  25. package/components/input.tsx +2 -2
  26. package/components/link.tsx +46 -16
  27. package/components/logger-popup.tsx +213 -0
  28. package/components/modal.tsx +32 -16
  29. package/components/plugin-module.tsx +62 -3
  30. package/components/price.tsx +2 -2
  31. package/components/select.tsx +3 -3
  32. package/components/selected-payment-option-view.tsx +21 -0
  33. package/data/client/account.ts +17 -2
  34. package/data/client/basket.ts +39 -0
  35. package/data/client/checkout.ts +336 -99
  36. package/data/client/misc.ts +13 -1
  37. package/data/server/category.ts +11 -9
  38. package/data/server/flatpage.ts +4 -1
  39. package/data/server/form.ts +4 -1
  40. package/data/server/landingpage.ts +4 -1
  41. package/data/server/list.ts +5 -4
  42. package/data/server/menu.ts +4 -1
  43. package/data/server/product.ts +97 -52
  44. package/data/server/seo.ts +4 -1
  45. package/data/server/special-page.ts +5 -4
  46. package/data/server/widget.ts +71 -1
  47. package/data/urls.ts +6 -3
  48. package/hocs/client/with-segment-defaults.tsx +2 -2
  49. package/hocs/server/with-segment-defaults.tsx +81 -20
  50. package/hooks/index.ts +3 -0
  51. package/hooks/use-localization.ts +24 -10
  52. package/hooks/use-logger-context.tsx +114 -0
  53. package/hooks/use-logger.ts +92 -0
  54. package/hooks/use-loyalty-availability.ts +21 -0
  55. package/hooks/use-payment-options.ts +2 -1
  56. package/hooks/use-pz-params.ts +37 -0
  57. package/hooks/use-router.ts +53 -19
  58. package/instrumentation/index.ts +0 -1
  59. package/instrumentation/node.ts +2 -20
  60. package/jest.config.js +25 -0
  61. package/lib/cache-handler.mjs +534 -16
  62. package/lib/cache.ts +269 -34
  63. package/localization/provider.tsx +2 -5
  64. package/middlewares/bfcache-headers.ts +18 -0
  65. package/middlewares/checkout-provider.ts +1 -1
  66. package/middlewares/complete-gpay.ts +32 -26
  67. package/middlewares/complete-masterpass.ts +33 -26
  68. package/middlewares/complete-wallet.ts +182 -0
  69. package/middlewares/default.ts +357 -203
  70. package/middlewares/index.ts +10 -2
  71. package/middlewares/locale.ts +5 -3
  72. package/middlewares/masterpass-rest-callback.ts +230 -0
  73. package/middlewares/oauth-login.ts +200 -57
  74. package/middlewares/pretty-url.ts +21 -8
  75. package/middlewares/redirection-payment.ts +32 -26
  76. package/middlewares/saved-card-redirection.ts +33 -26
  77. package/middlewares/three-d-redirection.ts +32 -26
  78. package/middlewares/url-redirection.ts +9 -15
  79. package/middlewares/wallet-complete-redirection.ts +206 -0
  80. package/package.json +24 -10
  81. package/plugins.d.ts +19 -4
  82. package/plugins.js +9 -1
  83. package/redux/actions.ts +47 -0
  84. package/redux/middlewares/checkout.ts +61 -8
  85. package/redux/middlewares/index.ts +14 -10
  86. package/redux/middlewares/pre-order/address.ts +1 -1
  87. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +1 -1
  88. package/redux/middlewares/pre-order/data-source-shipping-option.ts +1 -1
  89. package/redux/middlewares/pre-order/delivery-option.ts +1 -1
  90. package/redux/middlewares/pre-order/index.ts +3 -1
  91. package/redux/middlewares/pre-order/installment-option.ts +2 -1
  92. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  93. package/redux/middlewares/pre-order/payment-option.ts +1 -1
  94. package/redux/middlewares/pre-order/pre-order-validation.ts +4 -3
  95. package/redux/middlewares/pre-order/redirection.ts +2 -2
  96. package/redux/middlewares/pre-order/set-pre-order.ts +2 -2
  97. package/redux/middlewares/pre-order/shipping-option.ts +1 -1
  98. package/redux/middlewares/pre-order/shipping-step.ts +1 -1
  99. package/redux/reducers/checkout.ts +15 -1
  100. package/redux/reducers/index.ts +7 -1
  101. package/redux/reducers/widget.ts +80 -0
  102. package/sentry/index.ts +54 -17
  103. package/tailwind/content.js +16 -0
  104. package/types/commerce/checkout.ts +26 -1
  105. package/types/commerce/widget.ts +33 -0
  106. package/types/index.ts +114 -5
  107. package/types/next-auth.d.ts +2 -2
  108. package/types/widget.ts +80 -0
  109. package/utils/app-fetch.ts +7 -2
  110. package/utils/generate-commerce-search-params.ts +3 -2
  111. package/utils/get-checkout-path.ts +3 -0
  112. package/utils/get-root-hostname.ts +28 -0
  113. package/utils/index.ts +69 -18
  114. package/utils/mobile-3d-iframe.ts +8 -2
  115. package/utils/override-middleware.ts +1 -0
  116. package/utils/pz-segments.ts +92 -0
  117. package/utils/redirect-ignore.ts +35 -0
  118. package/utils/redirect.ts +9 -3
  119. package/utils/redirection-iframe.ts +8 -2
  120. package/utils/widget-styles.ts +107 -0
  121. package/with-pz-config.js +20 -7
@@ -1,10 +1,20 @@
1
- import settings from 'settings';
2
1
  import { JSX } from 'react';
3
- import { LayoutProps, PageProps, RootLayoutProps } from '../../types';
2
+ import settings from 'settings';
3
+ import {
4
+ ResolvedPageProps,
5
+ ResolvedLayoutProps,
6
+ ResolvedRootLayoutProps
7
+ } from '../../types';
4
8
  import { redirect } from 'next/navigation';
5
9
  import { ServerVariables } from '../../utils/server-variables';
6
10
  import { ROUTES } from 'routes';
7
11
  import logger from '../../utils/log';
12
+ import {
13
+ decodePzValue,
14
+ getPzSegmentsConfig,
15
+ getBuiltInSegments,
16
+ isLegacyMode
17
+ } from '../../utils/pz-segments';
8
18
 
9
19
  type SegmentType = 'root-layout' | 'layout' | 'page';
10
20
 
@@ -13,27 +23,66 @@ interface SegmentDefaultsOptions {
13
23
  }
14
24
 
15
25
  export const withSegmentDefaults =
16
- <T extends PageProps | LayoutProps | RootLayoutProps>(
26
+ <T extends ResolvedPageProps | ResolvedLayoutProps | ResolvedRootLayoutProps>(
17
27
  Component: (
18
28
  props?: T
19
29
  ) => null | JSX.Element | Promise<JSX.Element> | Promise<JSX.Element[]>,
20
30
  options: SegmentDefaultsOptions
21
31
  ) =>
22
- async (props: T) => {
23
- let componentProps = { ...props };
24
-
25
- const { locale, currency } = await props.params;
32
+ async (props: any) => {
33
+ const resolvedParams = await props.params;
34
+ const resolvedSearchParams = props.searchParams
35
+ ? await props.searchParams
36
+ : undefined;
37
+
38
+ // Normalize Next 16's raw Record<string, string | string[]> shape into a
39
+ // URLSearchParams instance so v1 brands (which expect URLSearchParams) can
40
+ // call .get/.has/etc without runtime breakage.
41
+ const normalizedSearchParams =
42
+ resolvedSearchParams instanceof URLSearchParams
43
+ ? resolvedSearchParams
44
+ : new URLSearchParams(
45
+ Object.entries(resolvedSearchParams ?? {}).flatMap(([k, v]) =>
46
+ Array.isArray(v)
47
+ ? v.map((item): [string, string] => [k, String(item)])
48
+ : v != null
49
+ ? ([[k, String(v)]] as [string, string][])
50
+ : []
51
+ )
52
+ );
53
+
54
+ let componentProps = {
55
+ ...props,
56
+ params: resolvedParams,
57
+ searchParams: normalizedSearchParams,
58
+ ...('children' in props ? { children: (props as any).children } : {})
59
+ } as T;
60
+
61
+ let localeValue: string;
62
+ let currencyValue: string;
63
+
64
+ if (isLegacyMode(settings)) {
65
+ localeValue = resolvedParams.locale;
66
+ currencyValue = resolvedParams.currency;
67
+ } else {
68
+ const pzConfig = getPzSegmentsConfig(settings);
69
+ const parsed = decodePzValue(resolvedParams.pz, pzConfig);
70
+ const builtIn = getBuiltInSegments(parsed, settings);
71
+ localeValue = builtIn.locale;
72
+ currencyValue = builtIn.currency;
73
+ }
26
74
 
27
75
  if (options.segmentType === 'root-layout') {
28
76
  componentProps = (await addRootLayoutProps(
29
- componentProps as RootLayoutProps
77
+ componentProps as ResolvedRootLayoutProps,
78
+ localeValue
30
79
  )) as T;
31
80
 
32
81
  checkRedisVariables();
33
82
  }
34
83
 
35
- ServerVariables.locale = await locale;
36
- ServerVariables.currency = await currency;
84
+ ServerVariables.locale = localeValue;
85
+ ServerVariables.currency = currencyValue;
37
86
 
38
87
  return await (
39
88
  <>
@@ -42,12 +91,21 @@ export const withSegmentDefaults =
42
91
  );
43
92
  };
44
93
 
45
- const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
46
- const params = await componentProps.params;
47
-
48
- if (
49
- params.commerce !== encodeURIComponent(decodeURI(settings.commerceUrl)) ||
50
- !settings.localization.locales.find((l) => l.value === params.locale)
94
+ const addRootLayoutProps = async (
95
+ componentProps: ResolvedRootLayoutProps,
96
+ localeValue: string
97
+ ) => {
98
+ if (isLegacyMode(settings)) {
99
+ const params = componentProps.params;
100
+ if (
101
+ params.commerce !==
102
+ encodeURIComponent(decodeURI(settings.commerceUrl)) ||
103
+ !settings.localization.locales.find((l) => l.value === localeValue)
104
+ ) {
105
+ return redirect(ROUTES.HOME);
106
+ }
107
+ } else if (
108
+ !settings.localization.locales.find((l) => l.value === localeValue)
51
109
  ) {
52
110
  return redirect(ROUTES.HOME);
53
111
  }
@@ -55,12 +113,12 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
55
113
  const { getTranslations } = settings.useOptimizedTranslations
56
114
  ? require('translations')
57
115
  : require('../../utils/server-translation');
58
- const translations = await getTranslations(params.locale);
116
+ const translations = await getTranslations(localeValue);
59
117
 
60
118
  componentProps.translations = translations;
61
119
 
62
120
  const locale = settings.localization.locales.find(
63
- (l) => l.value === params.locale
121
+ (l) => l.value === localeValue
64
122
  );
65
123
  const [isoCode] = locale.value.split('-');
66
124
 
@@ -75,10 +133,13 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
75
133
  const checkRedisVariables = () => {
76
134
  const requiredVariableValues = [
77
135
  process.env.CACHE_HOST,
78
- process.env.CACHE_PORT,
79
- process.env.CACHE_SECRET
136
+ process.env.CACHE_PORT
80
137
  ];
81
138
 
139
+ if (!settings.usePrettyUrlRoute) {
140
+ requiredVariableValues.push(process.env.CACHE_SECRET);
141
+ }
142
+
82
143
  if (
83
144
  !requiredVariableValues.every((v) => v) &&
84
145
  process.env.NODE_ENV === 'production'
package/hooks/index.ts CHANGED
@@ -10,4 +10,7 @@ export * from './use-mobile-iframe-handler';
10
10
  export * from './use-payment-options';
11
11
  export * from './use-pagination';
12
12
  export * from './use-message-listener';
13
+ export * from './use-logger';
14
+ export * from './use-logger-context';
13
15
  export * from './use-sentry-uncaught-errors';
16
+ export * from './use-pz-params';
@@ -25,19 +25,33 @@ export const useLocalization = () => {
25
25
  * @param locale Locale value defined in the settings.
26
26
  */
27
27
  const setLocale = (locale: string) => {
28
- const localePath =
29
- locale === defaultLocaleValue &&
30
- localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales
31
- ? ''
32
- : `/${locale}`;
28
+ const { protocol, hostname, port, search, pathname } = location;
29
+ const pathnameWithoutLocale = pathname.replace(urlLocaleMatcherRegex, '');
33
30
 
34
- const pathnameWithoutLocale = location.pathname.replace(
35
- urlLocaleMatcherRegex,
36
- ''
37
- );
31
+ let targetUrl;
32
+
33
+ if (localeUrlStrategy === LocaleUrlStrategy.Subdomain) {
34
+ const hostParts = hostname.split('.');
35
+ const subDomain = hostParts[0];
36
+ const isSubdomainLocale = locales.some((loc) => loc.value === subDomain);
37
+ const baseDomain = isSubdomainLocale
38
+ ? hostParts.slice(1).join('.')
39
+ : hostname;
40
+
41
+ const formattedPort = port ? `:${port}` : '';
42
+
43
+ targetUrl = `${protocol}//${locale}.${baseDomain}${formattedPort}${pathnameWithoutLocale}${search}`;
44
+ } else {
45
+ const shouldHideLocale =
46
+ locale === defaultLocaleValue &&
47
+ localeUrlStrategy !== LocaleUrlStrategy.ShowAllLocales;
48
+ const localePath = shouldHideLocale ? '' : `/${locale}`;
49
+
50
+ targetUrl = `${localePath}${pathnameWithoutLocale}${search}`;
51
+ }
38
52
 
39
53
  // router.push is not used because reloading the page also clears the client side cache.
40
- location.href = `${localePath}${pathnameWithoutLocale}${location.search}`;
54
+ location.href = targetUrl;
41
55
  };
42
56
 
43
57
  /**
@@ -0,0 +1,114 @@
1
+ 'use client';
2
+
3
+ import React, {
4
+ createContext,
5
+ useContext,
6
+ ReactNode,
7
+ useMemo,
8
+ useRef,
9
+ useEffect
10
+ } from 'react';
11
+ import { useLogger, LogEntry, LogLevel } from './use-logger';
12
+
13
+ const LOG_LEVELS: LogLevel[] = ['info', 'warn', 'error'];
14
+
15
+ interface LoggerContextType {
16
+ logs: LogEntry[];
17
+ isVisible: boolean;
18
+ toggleVisibility: () => void;
19
+ clearLogs: () => void;
20
+ info: (message: string, payload?: any) => string;
21
+ warn: (message: string, payload?: any) => string;
22
+ error: (message: string, payload?: any) => string;
23
+ isDevelopment: boolean;
24
+ hasError: boolean;
25
+ hasWarning: boolean;
26
+ }
27
+
28
+ const LoggerContext = createContext<LoggerContextType | undefined>(undefined);
29
+
30
+ let globalAddLogFunction:
31
+ | ((level: string, message: string, payload?: any) => string)
32
+ | null = null;
33
+
34
+ // temporary queue for logs generated before the logger is initialized
35
+ const pendingLogs: Array<{ level: string; message: string; payload?: any }> =
36
+ [];
37
+
38
+ const createLogFunction =
39
+ (level: LogLevel) => (message: string, payload?: any) => {
40
+ if (
41
+ typeof window !== 'undefined' &&
42
+ process.env.NODE_ENV === 'development'
43
+ ) {
44
+ try {
45
+ if (globalAddLogFunction) {
46
+ globalAddLogFunction(level, message, payload);
47
+ } else {
48
+ pendingLogs.push({ level, message, payload });
49
+ }
50
+ } catch (err) {
51
+ // prevent errors
52
+ }
53
+ }
54
+
55
+ return '';
56
+ };
57
+
58
+ const stableLogger = LOG_LEVELS.reduce((logger, level) => {
59
+ logger[level] = createLogFunction(level);
60
+
61
+ return logger;
62
+ }, {} as Record<LogLevel, (message: string, payload?: any) => string>);
63
+
64
+ export const LoggerProvider = ({ children }: { children: ReactNode }) => {
65
+ const loggerHook = useLogger();
66
+
67
+ const addLogRef = useRef<
68
+ (level: string, message: string, payload?: any) => string
69
+ >((level, message, payload) => {
70
+ if (LOG_LEVELS.includes(level as LogLevel)) {
71
+ return loggerHook[level as LogLevel](message, payload);
72
+ }
73
+ return '';
74
+ });
75
+
76
+ useEffect(() => {
77
+ globalAddLogFunction = addLogRef.current;
78
+
79
+ if (pendingLogs.length > 0) {
80
+ pendingLogs.forEach((log) => {
81
+ if (globalAddLogFunction) {
82
+ globalAddLogFunction(log.level, log.message, log.payload);
83
+ }
84
+ });
85
+
86
+ pendingLogs.length = 0;
87
+ }
88
+
89
+ return () => {
90
+ globalAddLogFunction = null;
91
+ };
92
+ }, []);
93
+
94
+ const contextValue = useMemo(
95
+ () => loggerHook,
96
+ [loggerHook.logs, loggerHook.isVisible, loggerHook.isDevelopment] // eslint-disable-line react-hooks/exhaustive-deps
97
+ );
98
+
99
+ return (
100
+ <LoggerContext.Provider value={contextValue}>
101
+ {children}
102
+ </LoggerContext.Provider>
103
+ );
104
+ };
105
+
106
+ export const useLoggerContext = () => {
107
+ const context = useContext(LoggerContext);
108
+ if (context === undefined) {
109
+ throw new Error('useLoggerContext must be used within a LoggerProvider');
110
+ }
111
+ return context;
112
+ };
113
+
114
+ export const devLogger = stableLogger;
@@ -0,0 +1,92 @@
1
+ import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
2
+
3
+ export type LogLevel = 'info' | 'warn' | 'error';
4
+
5
+ export interface LogEntry {
6
+ id: string;
7
+ level: LogLevel;
8
+ message: string;
9
+ timestamp: Date;
10
+ payload?: any;
11
+ }
12
+
13
+ const LOG_LEVELS: LogEntry['level'][] = ['info', 'warn', 'error'];
14
+
15
+ export function useLogger() {
16
+ const [logs, setLogs] = useState<LogEntry[]>([]);
17
+ const [isVisible, setIsVisible] = useState(false);
18
+ const logsRef = useRef<LogEntry[]>([]);
19
+ const [isDevelopment, setIsDevelopment] = useState(false);
20
+
21
+ useEffect(() => {
22
+ setIsDevelopment(
23
+ process.env.NODE_ENV === 'development' ||
24
+ window.location.hostname === 'localhost' ||
25
+ window.location.hostname === '127.0.0.1'
26
+ );
27
+ }, []);
28
+
29
+ useEffect(() => {
30
+ logsRef.current = logs;
31
+ }, [logs]);
32
+
33
+ const addLog = useCallback(
34
+ (level: LogEntry['level'], message: string, payload?: any) => {
35
+ const newLog: LogEntry = {
36
+ id: Math.random().toString(36).substring(2, 9),
37
+ level,
38
+ message,
39
+ timestamp: new Date(),
40
+ payload
41
+ };
42
+
43
+ setLogs((prevLogs) => [newLog, ...prevLogs]);
44
+
45
+ return newLog.id;
46
+ },
47
+ []
48
+ );
49
+
50
+ const clearLogs = useCallback(() => {
51
+ setLogs([]);
52
+ }, []);
53
+
54
+ const toggleVisibility = useCallback(() => {
55
+ setIsVisible((prev) => !prev);
56
+ }, []);
57
+
58
+ const createLogMethod = useCallback(
59
+ (level: LogEntry['level']) => (message: string, payload?: any) =>
60
+ addLog(level, message, payload),
61
+ [addLog]
62
+ );
63
+
64
+ const logMethods = useMemo(
65
+ () =>
66
+ LOG_LEVELS.reduce((methods, level) => {
67
+ methods[level] = createLogMethod(level);
68
+ return methods;
69
+ }, {} as Record<LogEntry['level'], (message: string, payload?: any) => string>),
70
+ [createLogMethod]
71
+ );
72
+
73
+ const hasError = useMemo(
74
+ () => logs.some((log) => log.level === 'error'),
75
+ [logs]
76
+ );
77
+ const hasWarning = useMemo(
78
+ () => logs.some((log) => log.level === 'warn'),
79
+ [logs]
80
+ );
81
+
82
+ return {
83
+ logs,
84
+ isVisible,
85
+ toggleVisibility,
86
+ clearLogs,
87
+ ...logMethods,
88
+ isDevelopment,
89
+ hasError,
90
+ hasWarning
91
+ };
92
+ }
@@ -0,0 +1,21 @@
1
+ import { useAppSelector } from '../redux/hooks';
2
+
3
+ export const useLoyaltyAvailability = () => {
4
+ const { paymentOptions, unavailablePaymentOptions } = useAppSelector(
5
+ (state) => state.checkout
6
+ );
7
+
8
+ const hasLoyaltyInAvailable = paymentOptions.some(
9
+ (option) =>
10
+ option.payment_type === 'loyalty_money' ||
11
+ option.payment_type === 'loyalty'
12
+ );
13
+
14
+ const hasLoyaltyInUnavailable = unavailablePaymentOptions.some(
15
+ (option) =>
16
+ option.payment_type === 'loyalty_money' ||
17
+ option.payment_type === 'loyalty'
18
+ );
19
+
20
+ return hasLoyaltyInAvailable || hasLoyaltyInUnavailable;
21
+ };
@@ -21,7 +21,8 @@ export const usePaymentOptions = () => {
21
21
  credit_payment: 'pz-credit-payment',
22
22
  masterpass: 'pz-masterpass',
23
23
  saved_card: 'pz-saved-card',
24
- gpay: 'pz-gpay'
24
+ gpay: 'pz-gpay',
25
+ masterpass_rest: 'pz-masterpass-rest'
25
26
  };
26
27
 
27
28
  const isInitialTypeIncluded = (type: string) => initialTypes.has(type);
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import { useParams } from 'next/navigation';
4
+ import settings from 'settings';
5
+ import {
6
+ decodePzValue,
7
+ getPzSegmentsConfig,
8
+ getBuiltInSegments,
9
+ isLegacyMode
10
+ } from '../utils/pz-segments';
11
+
12
+ export function usePzParams(): {
13
+ locale: string;
14
+ currency: string;
15
+ [key: string]: string;
16
+ } {
17
+ const params = useParams() as Record<string, string>;
18
+
19
+ if (isLegacyMode(settings)) {
20
+ return {
21
+ locale:
22
+ params.locale ?? settings.localization.defaultLocaleValue,
23
+ currency:
24
+ params.currency ?? settings.localization.defaultCurrencyCode
25
+ };
26
+ }
27
+
28
+ const pzValue = params.pz ?? '';
29
+ const config = getPzSegmentsConfig(settings);
30
+ const parsed = decodePzValue(pzValue, config);
31
+ const builtIn = getBuiltInSegments(parsed, settings);
32
+
33
+ return {
34
+ ...parsed,
35
+ ...builtIn
36
+ };
37
+ }
@@ -5,6 +5,24 @@ import { urlLocaleMatcherRegex } from '../utils';
5
5
  import { useLocalization } from './use-localization';
6
6
  import { LocaleUrlStrategy } from '../localization';
7
7
 
8
+ const isNavigableHref = (href: string) => {
9
+ if (!href || typeof href !== 'string') return false;
10
+
11
+ const trimmedHref = href.trim();
12
+ if (!trimmedHref) return false;
13
+ if (trimmedHref === '#') return true;
14
+ if (trimmedHref.startsWith('mailto:') || trimmedHref.startsWith('tel:')) {
15
+ return true;
16
+ }
17
+
18
+ try {
19
+ new URL(trimmedHref, 'http://localhost');
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ };
25
+
8
26
  export const useRouter = () => {
9
27
  const { locale, locales, localeUrlStrategy, defaultLocaleValue } =
10
28
  useLocalization();
@@ -16,27 +34,43 @@ export const useRouter = () => {
16
34
  href: string,
17
35
  options?
18
36
  ) => {
19
- if (href.startsWith('http')) {
20
- return fn(href, options);
37
+ if (!isNavigableHref(href)) {
38
+ return fn('#', options);
39
+ }
40
+
41
+ const trimmedHref = href.trim();
42
+
43
+ if (
44
+ trimmedHref.startsWith('http') ||
45
+ trimmedHref.startsWith('//') ||
46
+ trimmedHref.startsWith('mailto:') ||
47
+ trimmedHref.startsWith('tel:') ||
48
+ trimmedHref.startsWith('#')
49
+ ) {
50
+ return fn(trimmedHref, options);
21
51
  }
22
52
 
23
- const url = new URL(href, window.location.origin);
24
- const pathnameWithoutLocale = url.pathname.replace(
25
- urlLocaleMatcherRegex,
26
- ''
27
- );
28
-
29
- url.pathname = `${
30
- localeUrlStrategy === LocaleUrlStrategy.Subdomain ||
31
- (locale === defaultLocale?.value &&
32
- [LocaleUrlStrategy.HideDefaultLocale].includes(
33
- localeUrlStrategy as LocaleUrlStrategy
34
- ))
35
- ? ''
36
- : `/${locale}`
37
- }${pathnameWithoutLocale}`;
38
-
39
- return fn(url.href, options);
53
+ try {
54
+ const url = new URL(trimmedHref, window.location.origin);
55
+ const pathnameWithoutLocale = url.pathname.replace(
56
+ urlLocaleMatcherRegex,
57
+ ''
58
+ );
59
+
60
+ url.pathname = `${
61
+ localeUrlStrategy === LocaleUrlStrategy.Subdomain ||
62
+ (locale === defaultLocale?.value &&
63
+ [LocaleUrlStrategy.HideDefaultLocale].includes(
64
+ localeUrlStrategy as LocaleUrlStrategy
65
+ ))
66
+ ? ''
67
+ : `/${locale}`
68
+ }${pathnameWithoutLocale}`;
69
+
70
+ return fn(url.href, options);
71
+ } catch {
72
+ return fn('#', options);
73
+ }
40
74
  };
41
75
 
42
76
  return {
@@ -3,7 +3,6 @@ import * as Sentry from '@sentry/nextjs';
3
3
 
4
4
  export async function register() {
5
5
  if (process.env.NEXT_RUNTIME === 'nodejs') {
6
- await import('./node');
7
6
  initSentry('Server');
8
7
  }
9
8
 
@@ -1,20 +1,2 @@
1
- import { NodeSDK } from '@opentelemetry/sdk-node';
2
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
3
- import { Resource } from '@opentelemetry/resources';
4
- import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
5
- import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
6
-
7
- const sdk = new NodeSDK({
8
- resource: new Resource({
9
- [SemanticResourceAttributes.SERVICE_NAME]: 'pz-next-app'
10
- }),
11
- spanProcessor: new SimpleSpanProcessor(
12
- new OTLPTraceExporter({
13
- url: `${
14
- process.env.PZ_DASHBOARD_URL ?? 'http://localhost:3005'
15
- }/api/traces`
16
- })
17
- )
18
- });
19
-
20
- sdk.start();
1
+ // OpenTelemetry tracing is handled by Sentry.
2
+ // Custom NodeSDK setup removed due to version incompatibility with Sentry 10's OpenTelemetry v2.x requirements.
package/jest.config.js ADDED
@@ -0,0 +1,25 @@
1
+ const path = require('path');
2
+ const findBaseDir = require('./utils/find-base-dir');
3
+
4
+ const baseDir = findBaseDir();
5
+
6
+ module.exports = {
7
+ preset: 'ts-jest',
8
+ testEnvironment: 'node',
9
+ rootDir: path.resolve(__dirname),
10
+ testMatch: ['**/*.test.ts'],
11
+ testPathIgnorePatterns: [],
12
+ roots: [path.resolve(__dirname)],
13
+ transformIgnorePatterns: [],
14
+ moduleNameMapper: {
15
+ '^settings$': path.resolve(baseDir, 'src/settings.js')
16
+ },
17
+ transform: {
18
+ '^.+\\.(tsx?|jsx?|mjs?)$': [
19
+ 'ts-jest',
20
+ {
21
+ tsconfig: path.resolve(__dirname, '__tests__/tsconfig.json')
22
+ }
23
+ ]
24
+ }
25
+ };