@akinon/next 1.113.0-snapshot-ZERO-3847-20251204102949 → 1.113.0-snapshot-ZERO-3878-20251204105428

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,6 +1,11 @@
1
1
  # @akinon/next
2
2
 
3
- ## 1.113.0-snapshot-ZERO-3847-20251204102949
3
+ ## 1.113.0-snapshot-ZERO-3878-20251204105428
4
+
5
+ ### Minor Changes
6
+
7
+ - b59bed4: ZERO-3878: Reintegrated similar products and removed localStorage checks and implemented props based showing of buttons
8
+ - cbcfdf4: ZERO-3859: Haso payment gateway implmeneted
4
9
 
5
10
  ## 1.112.0
6
11
 
@@ -0,0 +1,75 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ async function proxyImage(imageUrl: string) {
4
+ if (!imageUrl) {
5
+ return NextResponse.json(
6
+ { success: false, error: 'Missing url parameter' },
7
+ { status: 400 }
8
+ );
9
+ }
10
+
11
+ const imageResponse = await fetch(imageUrl);
12
+
13
+ if (!imageResponse.ok) {
14
+ return NextResponse.json(
15
+ { success: false, error: 'Failed to fetch image from URL' },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ const imageBuffer = await imageResponse.arrayBuffer();
21
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
22
+ const contentType = imageResponse.headers.get('content-type') || 'image/jpeg';
23
+
24
+ return { base64Image, contentType, imageBuffer };
25
+ }
26
+
27
+ export async function GET(request: NextRequest) {
28
+ try {
29
+ const { searchParams } = new URL(request.url);
30
+ const imageUrl = searchParams.get('url');
31
+
32
+ const result = await proxyImage(imageUrl!);
33
+ if (result instanceof NextResponse) {
34
+ return result;
35
+ }
36
+
37
+ return new NextResponse(result.imageBuffer, {
38
+ status: 200,
39
+ headers: {
40
+ 'Content-Type': result.contentType,
41
+ 'Access-Control-Allow-Origin': '*',
42
+ 'Cache-Control': 'public, max-age=3600'
43
+ }
44
+ });
45
+ } catch (error) {
46
+ console.error('Image proxy error:', error);
47
+ return NextResponse.json(
48
+ { success: false, error: 'Internal server error' },
49
+ { status: 500 }
50
+ );
51
+ }
52
+ }
53
+
54
+ export async function POST(request: NextRequest) {
55
+ try {
56
+ const body = await request.json();
57
+ const imageUrl = body.imageUrl;
58
+
59
+ const result = await proxyImage(imageUrl);
60
+ if (result instanceof NextResponse) {
61
+ return result;
62
+ }
63
+
64
+ return NextResponse.json({
65
+ success: true,
66
+ base64Image: `data:${result.contentType};base64,${result.base64Image}`
67
+ });
68
+ } catch (error) {
69
+ console.error('Image proxy POST error:', error);
70
+ return NextResponse.json(
71
+ { success: false, error: 'Internal server error' },
72
+ { status: 500 }
73
+ );
74
+ }
75
+ }
@@ -0,0 +1,63 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const { searchParams } = new URL(request.url);
7
+ const dynamicFilter = request.headers.get('x-search-dynamic-filter');
8
+ const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
9
+
10
+ if (!dynamicFilter && !dynamicExclude) {
11
+ return NextResponse.json(
12
+ {
13
+ error:
14
+ 'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
15
+ },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ if (Settings.commerceUrl === 'default') {
21
+ return NextResponse.json(
22
+ { error: 'Commerce URL is not configured' },
23
+ { status: 500 }
24
+ );
25
+ }
26
+
27
+ const queryString = searchParams.toString();
28
+ const apiUrl = `${Settings.commerceUrl}/list${
29
+ queryString ? `?${queryString}` : ''
30
+ }`;
31
+
32
+ const headers: Record<string, string> = {
33
+ Accept: 'application/json',
34
+ 'Content-Type': 'application/json'
35
+ };
36
+
37
+ if (dynamicFilter) {
38
+ headers['x-search-dynamic-filter'] = dynamicFilter;
39
+ } else if (dynamicExclude) {
40
+ headers['x-search-dynamic-exclude'] = dynamicExclude;
41
+ }
42
+
43
+ const response = await fetch(apiUrl, {
44
+ method: 'GET',
45
+ headers
46
+ });
47
+
48
+ if (!response.ok) {
49
+ return NextResponse.json(
50
+ { error: `API request failed with status: ${response.status}` },
51
+ { status: response.status }
52
+ );
53
+ }
54
+
55
+ const data = await response.json();
56
+ return NextResponse.json(data);
57
+ } catch (error) {
58
+ return NextResponse.json(
59
+ { error: (error as Error).message },
60
+ { status: 500 }
61
+ );
62
+ }
63
+ }
@@ -0,0 +1,111 @@
1
+ import { NextResponse } from 'next/server';
2
+ import Settings from 'settings';
3
+
4
+ const IMAGE_SEARCH_API_URL = Settings.commerceUrl + '/image-search/';
5
+
6
+ const errorResponse = (message: string, status: number) => {
7
+ return NextResponse.json({ error: message }, { status });
8
+ };
9
+
10
+ export async function GET(request: Request) {
11
+ const { searchParams } = new URL(request.url);
12
+ const limit = searchParams.get('limit') || '20';
13
+ const url = searchParams.get('url');
14
+ const excludedProductIds = searchParams.get('excluded_product_ids');
15
+
16
+ if (!url) {
17
+ return errorResponse('URL parameter is required', 400);
18
+ }
19
+
20
+ if (Settings.commerceUrl === 'default') {
21
+ return errorResponse('Commerce URL is not configured', 500);
22
+ }
23
+
24
+ const apiParams = new URLSearchParams();
25
+ apiParams.append('limit', limit);
26
+ apiParams.append('url', url);
27
+
28
+ if (excludedProductIds) {
29
+ apiParams.append('excluded_product_ids', excludedProductIds);
30
+ }
31
+
32
+ const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
33
+
34
+ try {
35
+ const response = await fetch(apiUrl, {
36
+ method: 'GET',
37
+ headers: {
38
+ Accept: 'application/json'
39
+ }
40
+ });
41
+
42
+ if (!response.ok) {
43
+ const errorText = await response.text();
44
+ return errorResponse(
45
+ errorText || `API request failed with status: ${response.status}`,
46
+ response.status
47
+ );
48
+ }
49
+
50
+ const responseText = await response.text();
51
+
52
+ return NextResponse.json(JSON.parse(responseText));
53
+ } catch (error) {
54
+ return errorResponse((error as Error).message, 500);
55
+ }
56
+ }
57
+
58
+ export async function POST(request: Request) {
59
+ const { searchParams } = new URL(request.url);
60
+ const limit = searchParams.get('limit') || '20';
61
+
62
+ if (Settings.commerceUrl === 'default') {
63
+ return errorResponse('Commerce URL is not configured', 500);
64
+ }
65
+
66
+ let requestBody;
67
+ try {
68
+ requestBody = await request.json();
69
+ } catch (error) {
70
+ return errorResponse('Invalid JSON in request body', 400);
71
+ }
72
+
73
+ if (!requestBody.image) {
74
+ return errorResponse('Image data is required in request body', 400);
75
+ }
76
+
77
+ const apiParams = new URLSearchParams();
78
+ apiParams.append('limit', limit);
79
+
80
+ const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
81
+
82
+ const bodyData: any = { image: requestBody.image };
83
+
84
+ if (requestBody.excluded_product_ids) {
85
+ bodyData.excluded_product_ids = requestBody.excluded_product_ids;
86
+ }
87
+
88
+ try {
89
+ const response = await fetch(apiUrl, {
90
+ method: 'POST',
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ Accept: 'application/json'
94
+ },
95
+ body: JSON.stringify(bodyData)
96
+ });
97
+
98
+ if (!response.ok) {
99
+ const errorText = await response.text();
100
+ return errorResponse(
101
+ errorText || `API request failed with status: ${response.status}`,
102
+ response.status
103
+ );
104
+ }
105
+
106
+ const responseData = await response.json();
107
+ return NextResponse.json(responseData);
108
+ } catch (error) {
109
+ return errorResponse((error as Error).message, 500);
110
+ }
111
+ }
@@ -24,7 +24,9 @@ enum Plugin {
24
24
  FlowPayment = 'pz-flow-payment',
25
25
  VirtualTryOn = 'pz-virtual-try-on',
26
26
  Hepsipay = 'pz-hepsipay',
27
- MasterpassRest = 'pz-masterpass-rest'
27
+ MasterpassRest = 'pz-masterpass-rest',
28
+ SimilarProducts = 'pz-similar-products',
29
+ Haso = 'pz-haso'
28
30
  }
29
31
 
30
32
  export enum Component {
@@ -55,7 +57,15 @@ export enum Component {
55
57
  IyzicoSavedCard = 'IyzicoSavedCardOption',
56
58
  Hepsipay = 'Hepsipay',
57
59
  FlowPayment = 'FlowPayment',
58
- MasterpassRest = 'MasterpassRestOption'
60
+ MasterpassRest = 'MasterpassRestOption',
61
+ SimilarProductsModal = 'SimilarProductsModal',
62
+ SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
63
+ SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
64
+ SimilarProductsPlugin = 'SimilarProductsPlugin',
65
+ ProductImageSearchFeature = 'ProductImageSearchFeature',
66
+ ImageSearchButton = 'ImageSearchButton',
67
+ HeaderImageSearchFeature = 'HeaderImageSearchFeature',
68
+ HasoPaymentGateway = 'HasoPaymentGateway'
59
69
  }
60
70
 
61
71
  const PluginComponents = new Map([
@@ -88,6 +98,18 @@ const PluginComponents = new Map([
88
98
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
89
99
  ],
90
100
  [Plugin.MultiBasket, [Component.MultiBasket]],
101
+ [
102
+ Plugin.SimilarProducts,
103
+ [
104
+ Component.SimilarProductsModal,
105
+ Component.SimilarProductsFilterSidebar,
106
+ Component.SimilarProductsResultsGrid,
107
+ Component.SimilarProductsPlugin,
108
+ Component.ProductImageSearchFeature,
109
+ Component.ImageSearchButton,
110
+ Component.HeaderImageSearchFeature
111
+ ]
112
+ ],
91
113
  [Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
92
114
  [Plugin.SavedCard, [Component.SavedCard]],
93
115
  [Plugin.FlowPayment, [Component.FlowPayment]],
@@ -97,7 +119,8 @@ const PluginComponents = new Map([
97
119
  ],
98
120
  [Plugin.Hepsipay, [Component.Hepsipay]],
99
121
  [Plugin.Hepsipay, [Component.Hepsipay]],
100
- [Plugin.MasterpassRest, [Component.MasterpassRest]]
122
+ [Plugin.MasterpassRest, [Component.MasterpassRest]],
123
+ [Plugin.Haso, [Component.HasoPaymentGateway]]
101
124
  ]);
102
125
 
103
126
  const getPlugin = (component: Component) => {
@@ -170,6 +193,10 @@ export default function PluginModule({
170
193
  promise = import(`${'@akinon/pz-virtual-try-on'}`);
171
194
  } else if (plugin === Plugin.MasterpassRest) {
172
195
  promise = import(`${'@akinon/pz-masterpass-rest'}`);
196
+ } else if (plugin === Plugin.SimilarProducts) {
197
+ promise = import(`${'@akinon/pz-similar-products'}`);
198
+ } else if (plugin === Plugin.Haso) {
199
+ promise = import(`${'@akinon/pz-haso'}`);
173
200
  }
174
201
  } catch (error) {
175
202
  logger.error(error);
@@ -463,6 +463,14 @@ CacheHandler.onCreation(async () => {
463
463
  set: async (key, value, context) => {
464
464
  const vKey = versionKey(key);
465
465
 
466
+ const stripTags = (val) => {
467
+ if (val && typeof val === 'object') {
468
+ const { tags, ...rest } = val;
469
+ return { ...rest, tags: [] };
470
+ }
471
+ return val;
472
+ };
473
+
466
474
  let compressedValue;
467
475
  let shouldUseCompressed = false;
468
476
 
@@ -482,12 +490,12 @@ CacheHandler.onCreation(async () => {
482
490
 
483
491
  if (shouldUseCompressed) {
484
492
  try {
485
- await redisHandler.set(vKey, compressedValue, context);
493
+ await redisHandler.set(vKey, stripTags(compressedValue), context);
486
494
 
487
495
  redisSetResult = { status: 'fulfilled' };
488
496
  } catch (compressionError) {
489
497
  try {
490
- await redisHandler.set(vKey, value, context);
498
+ await redisHandler.set(vKey, stripTags(value), context);
491
499
 
492
500
  redisSetResult = { status: 'fulfilled' };
493
501
  } catch (fallbackError) {
@@ -496,7 +504,7 @@ CacheHandler.onCreation(async () => {
496
504
  }
497
505
  } else {
498
506
  try {
499
- await redisHandler.set(vKey, value, context);
507
+ await redisHandler.set(vKey, stripTags(value), context);
500
508
  redisSetResult = { status: 'fulfilled' };
501
509
  } catch (error) {
502
510
  redisSetResult = { status: 'rejected', reason: error };
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.113.0-snapshot-ZERO-3847-20251204102949",
4
+ "version": "1.113.0-snapshot-ZERO-3878-20251204105428",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "set-cookie-parser": "2.6.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@akinon/eslint-plugin-projectzero": "1.113.0-snapshot-ZERO-3847-20251204102949",
38
+ "@akinon/eslint-plugin-projectzero": "1.113.0-snapshot-ZERO-3878-20251204105428",
39
39
  "@babel/core": "7.26.10",
40
40
  "@babel/preset-env": "7.26.9",
41
41
  "@babel/preset-typescript": "7.27.0",
package/plugins.js CHANGED
@@ -20,5 +20,7 @@ module.exports = [
20
20
  'pz-hepsipay',
21
21
  'pz-flow-payment',
22
22
  'pz-virtual-try-on',
23
- 'pz-masterpass-rest'
23
+ 'pz-masterpass-rest',
24
+ 'pz-similar-products',
25
+ 'pz-haso'
24
26
  ];