@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20

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 (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  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/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. package/with-pz-config.js +13 -6
@@ -0,0 +1,1379 @@
1
+ 'use client';
2
+
3
+ import { yupResolver } from '@hookform/resolvers/yup';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import clsx from 'clsx';
6
+ import React, { useMemo, useState } from 'react';
7
+ import { SubmitHandler, useForm } from 'react-hook-form';
8
+ import { twMerge } from 'tailwind-merge';
9
+ import * as yup from 'yup';
10
+
11
+ import { useGetAnonymousTrackingMutation } from '../../../data/client/user';
12
+ import { account } from '../../../data/urls';
13
+ import { Order, OrderItem } from '../../../types/commerce/order';
14
+ import { buildClientRequestUrl } from '../../../utils';
15
+ import { Image } from '../../image';
16
+ import ThemeBlock, { Block } from '../theme-block';
17
+ import { WithDesignerFeatures } from '../components/with-designer-features';
18
+ import { useThemeSettingsContext } from '../theme-settings-context';
19
+ import { Section } from '../theme-section';
20
+ import {
21
+ getCSSStyles,
22
+ getResponsiveValue,
23
+ resolveThemeCssVariables
24
+ } from '../utils';
25
+
26
+ interface OrderTrackingLookupSectionProps {
27
+ section: Section;
28
+ currentBreakpoint?: string;
29
+ placeholderId?: string;
30
+ isDesigner?: boolean;
31
+ selectedBlockId?: string | null;
32
+ }
33
+
34
+ interface OrderTrackingLookupFormType {
35
+ email: string;
36
+ number: string;
37
+ }
38
+
39
+ interface OrdersListResponse {
40
+ count: number;
41
+ results: Order[];
42
+ }
43
+
44
+ const readProperty = (
45
+ properties: Record<string, unknown> | undefined,
46
+ key: string,
47
+ fallback: string,
48
+ breakpoint: string
49
+ ): string => {
50
+ if (!properties) return fallback;
51
+ const raw = properties[key];
52
+ if (raw === undefined || raw === null) return fallback;
53
+ if (typeof raw === 'string') return raw;
54
+ if (typeof raw === 'object') {
55
+ const responsive = raw as Record<string, unknown>;
56
+ const picked =
57
+ responsive[breakpoint] ??
58
+ responsive.desktop ??
59
+ responsive.mobile ??
60
+ Object.values(responsive)[0];
61
+ return picked == null ? fallback : String(picked);
62
+ }
63
+ return String(raw);
64
+ };
65
+
66
+ const readBoolean = (
67
+ properties: Record<string, unknown> | undefined,
68
+ key: string,
69
+ fallback: boolean,
70
+ breakpoint: string
71
+ ): boolean => {
72
+ const value = getResponsiveValue(properties?.[key], breakpoint, fallback);
73
+ if (typeof value === 'boolean') return value;
74
+ if (typeof value === 'string') {
75
+ const normalized = value.trim().toLowerCase();
76
+ if (normalized === 'true') return true;
77
+ if (normalized === 'false') return false;
78
+ }
79
+ return fallback;
80
+ };
81
+
82
+ const readNumber = (
83
+ properties: Record<string, unknown> | undefined,
84
+ key: string,
85
+ fallback: number,
86
+ breakpoint: string
87
+ ): number => {
88
+ const value = getResponsiveValue(properties?.[key], breakpoint, fallback);
89
+ const numeric =
90
+ typeof value === 'number' ? value : Number.parseInt(String(value), 10);
91
+ return Number.isFinite(numeric) ? numeric : fallback;
92
+ };
93
+
94
+ const escapeHtml = (value: string): string =>
95
+ value
96
+ .replace(/&/g, '&amp;')
97
+ .replace(/</g, '&lt;')
98
+ .replace(/>/g, '&gt;')
99
+ .replace(/"/g, '&quot;')
100
+ .replace(/'/g, '&#39;');
101
+
102
+ const toHtmlText = (value: string): string => `<p>${escapeHtml(value)}</p>`;
103
+
104
+ const resolveResponsiveString = (
105
+ value: unknown,
106
+ breakpoint: string
107
+ ): string => {
108
+ if (typeof value === 'string') return value;
109
+
110
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
111
+ const responsiveValue = getResponsiveValue(value, breakpoint, '');
112
+ if (typeof responsiveValue === 'string') {
113
+ return responsiveValue;
114
+ }
115
+ }
116
+
117
+ return '';
118
+ };
119
+
120
+ const getOrderTrackingRole = (
121
+ block: Pick<Block, 'properties'>,
122
+ breakpoint: string
123
+ ): string =>
124
+ resolveResponsiveString(block.properties?.orderTrackingRole, breakpoint);
125
+
126
+ const cloneOrderTrackingBlock = (
127
+ block: Block,
128
+ overrides?: {
129
+ properties?: Record<string, unknown>;
130
+ value?: unknown;
131
+ }
132
+ ): Block => ({
133
+ ...block,
134
+ styleSourceId: block.styleSourceId || block.id,
135
+ value:
136
+ overrides && Object.prototype.hasOwnProperty.call(overrides, 'value')
137
+ ? overrides.value
138
+ : block.value,
139
+ properties: {
140
+ ...(block.properties || {}),
141
+ ...(overrides?.properties || {})
142
+ },
143
+ styles: { ...(block.styles || {}) },
144
+ blocks: block.blocks?.map((childBlock) => cloneOrderTrackingBlock(childBlock))
145
+ });
146
+
147
+ const findOrderTrackingRoleBlock = (
148
+ blocks: Block[],
149
+ role: string,
150
+ breakpoint: string
151
+ ): Block | null => {
152
+ for (const block of blocks) {
153
+ if (getOrderTrackingRole(block, breakpoint) === role) {
154
+ return block;
155
+ }
156
+
157
+ if (block.blocks?.length) {
158
+ const nestedMatch = findOrderTrackingRoleBlock(
159
+ block.blocks,
160
+ role,
161
+ breakpoint
162
+ );
163
+
164
+ if (nestedMatch) {
165
+ return nestedMatch;
166
+ }
167
+ }
168
+ }
169
+
170
+ return null;
171
+ };
172
+
173
+ const CONTROL_LAYOUT_STYLE_KEYS = new Set([
174
+ 'display',
175
+ 'width',
176
+ 'height',
177
+ 'min-width',
178
+ 'min-height',
179
+ 'max-width',
180
+ 'max-height',
181
+ 'margin',
182
+ 'margin-top',
183
+ 'margin-right',
184
+ 'margin-bottom',
185
+ 'margin-left',
186
+ 'padding',
187
+ 'padding-top',
188
+ 'padding-right',
189
+ 'padding-bottom',
190
+ 'padding-left',
191
+ 'font-size',
192
+ 'font-weight',
193
+ 'line-height',
194
+ 'align-items',
195
+ 'justify-content'
196
+ ]);
197
+
198
+ const normalizeOrderResponse = (response: unknown): Order | null => {
199
+ if (!response) return null;
200
+ if (Array.isArray(response)) {
201
+ return (response[0] as Order | undefined) || null;
202
+ }
203
+ return response as Order;
204
+ };
205
+
206
+ const resolveTrackingUrl = (order: Order | null): string => {
207
+ if (!order) return '';
208
+ if (typeof order.tracking_url === 'string' && order.tracking_url) {
209
+ return order.tracking_url;
210
+ }
211
+
212
+ const trackedItem = order.orderitem_set?.find((item) => {
213
+ const trackingUrl = (item as OrderItem & { tracking_url?: string })
214
+ .tracking_url;
215
+ return typeof trackingUrl === 'string' && trackingUrl.length > 0;
216
+ }) as (OrderItem & { tracking_url?: string }) | undefined;
217
+
218
+ return trackedItem?.tracking_url || '';
219
+ };
220
+
221
+ const resolveTrackingNumber = (order: Order | null): string => {
222
+ if (!order) return '';
223
+ if (typeof order.tracking_number === 'string' && order.tracking_number) {
224
+ return order.tracking_number;
225
+ }
226
+
227
+ const trackedItem = order.orderitem_set?.find(
228
+ (item) => typeof (item as OrderItem & { tracking_number?: string }).tracking_number === 'string'
229
+ ) as (OrderItem & { tracking_number?: string }) | undefined;
230
+
231
+ return trackedItem?.tracking_number || '';
232
+ };
233
+
234
+ const formatAmount = (value: unknown, currency: unknown): string => {
235
+ const amount = String(value ?? '').trim();
236
+ if (!amount) return '-';
237
+
238
+ const currencyLabel =
239
+ (currency as { label?: string; value?: string } | undefined)?.label ||
240
+ (currency as { label?: string; value?: string } | undefined)?.value ||
241
+ '';
242
+
243
+ if (!currencyLabel || amount.toUpperCase().includes(currencyLabel.toUpperCase())) {
244
+ return amount;
245
+ }
246
+
247
+ return `${amount} ${currencyLabel}`;
248
+ };
249
+
250
+ const formatDate = (value: string | undefined, locale: string): string => {
251
+ if (!value) return '-';
252
+ const date = new Date(value);
253
+ if (Number.isNaN(date.getTime())) return '-';
254
+
255
+ return new Intl.DateTimeFormat(locale || 'en', {
256
+ day: 'numeric',
257
+ month: 'long',
258
+ year: 'numeric'
259
+ }).format(date);
260
+ };
261
+
262
+ const getStatusTone = (
263
+ statusText: string
264
+ ): { background: string; color: string } => {
265
+ const normalized = statusText.toLowerCase();
266
+
267
+ if (
268
+ normalized.includes('deliver') ||
269
+ normalized.includes('teslim') ||
270
+ normalized.includes('completed')
271
+ ) {
272
+ return { background: '#dcfce7', color: '#166534' };
273
+ }
274
+
275
+ if (
276
+ normalized.includes('ship') ||
277
+ normalized.includes('kargo') ||
278
+ normalized.includes('gonder') ||
279
+ normalized.includes('hazir')
280
+ ) {
281
+ return { background: '#dbeafe', color: '#1d4ed8' };
282
+ }
283
+
284
+ if (
285
+ normalized.includes('cancel') ||
286
+ normalized.includes('iptal') ||
287
+ normalized.includes('return') ||
288
+ normalized.includes('iade')
289
+ ) {
290
+ return { background: '#fee2e2', color: '#b91c1c' };
291
+ }
292
+
293
+ return { background: '#fef3c7', color: '#92400e' };
294
+ };
295
+
296
+ const extractErrorMessage = (
297
+ error: unknown,
298
+ fallback: string
299
+ ): string => {
300
+ if (!error || typeof error !== 'object') return fallback;
301
+
302
+ const errorData = (error as { data?: Record<string, unknown> }).data;
303
+ if (!errorData || typeof errorData !== 'object') return fallback;
304
+
305
+ const flattenedValues = Object.values(errorData).flatMap((value) => {
306
+ if (typeof value === 'string') return [value];
307
+ if (Array.isArray(value)) {
308
+ return value.filter((item): item is string => typeof item === 'string');
309
+ }
310
+ return [];
311
+ });
312
+
313
+ return flattenedValues[0] || fallback;
314
+ };
315
+
316
+ const fetchJson = async <T,>(url: string): Promise<T | null> => {
317
+ const response = await fetch(url, {
318
+ method: 'GET',
319
+ headers: {
320
+ Accept: 'application/json'
321
+ }
322
+ });
323
+
324
+ if (!response.ok) {
325
+ return null;
326
+ }
327
+
328
+ return (await response.json()) as T;
329
+ };
330
+
331
+ const findMatchingOrderInPagedResponse = async (
332
+ buildUrl: (page: number) => string,
333
+ orderNumber: string
334
+ ): Promise<Order | null> => {
335
+ const firstPage = await fetchJson<OrdersListResponse>(buildUrl(1));
336
+ if (!firstPage?.results?.length) {
337
+ return null;
338
+ }
339
+
340
+ const normalize = (value: string | number | undefined | null) =>
341
+ String(value ?? '').trim();
342
+
343
+ const firstMatch = firstPage.results.find(
344
+ (order) => normalize(order.number) === normalize(orderNumber)
345
+ );
346
+ if (firstMatch) {
347
+ return firstMatch;
348
+ }
349
+
350
+ const totalPages = Math.min(
351
+ Math.ceil((firstPage.count || firstPage.results.length) / 100),
352
+ 5
353
+ );
354
+
355
+ for (let page = 2; page <= totalPages; page += 1) {
356
+ const currentPage = await fetchJson<OrdersListResponse>(buildUrl(page));
357
+ const currentMatch = currentPage?.results?.find(
358
+ (order) => normalize(order.number) === normalize(orderNumber)
359
+ );
360
+
361
+ if (currentMatch) {
362
+ return currentMatch;
363
+ }
364
+ }
365
+
366
+ return null;
367
+ };
368
+
369
+ const findAuthenticatedOrder = async (
370
+ orderNumber: string
371
+ ): Promise<Order | null> => {
372
+ const buildCurrentOrdersUrl = (page: number) =>
373
+ buildClientRequestUrl(account.getOrders({ page, limit: 100 }));
374
+ const buildOldOrdersUrl = (page: number) =>
375
+ buildClientRequestUrl(account.getOldOrders({ page, limit: 100 }));
376
+
377
+ return (
378
+ (await findMatchingOrderInPagedResponse(buildCurrentOrdersUrl, orderNumber)) ||
379
+ (await findMatchingOrderInPagedResponse(buildOldOrdersUrl, orderNumber))
380
+ );
381
+ };
382
+
383
+ export default function OrderTrackingLookupSection({
384
+ section,
385
+ currentBreakpoint = 'desktop',
386
+ placeholderId = '',
387
+ isDesigner = false,
388
+ selectedBlockId = null
389
+ }: OrderTrackingLookupSectionProps) {
390
+ const themeSettings = useThemeSettingsContext();
391
+ const { locale, t } = useLocalization();
392
+ const [getAnonymousTracking] = useGetAnonymousTrackingMutation();
393
+ const [lookupOrder, setLookupOrder] = useState<Order | null>(null);
394
+ const [lookupError, setLookupError] = useState('');
395
+ const [isSubmitting, setIsSubmitting] = useState(false);
396
+
397
+ const props = section.properties || {};
398
+ const orderNumberPlaceholder = readProperty(
399
+ props,
400
+ 'order-number-placeholder',
401
+ t('account.anonymous_order.form.order_id'),
402
+ currentBreakpoint
403
+ );
404
+ const emailPlaceholder = readProperty(
405
+ props,
406
+ 'email-placeholder',
407
+ t('account.anonymous_order.form.email'),
408
+ currentBreakpoint
409
+ );
410
+ const buttonText = readProperty(
411
+ props,
412
+ 'button-text',
413
+ t('account.anonymous_order.form.submit_button'),
414
+ currentBreakpoint
415
+ );
416
+ const helperText = readProperty(
417
+ props,
418
+ 'helper-text',
419
+ 'Enter your order number and the e-mail used at checkout to see live shipment details.',
420
+ currentBreakpoint
421
+ );
422
+ const errorMessage = readProperty(
423
+ props,
424
+ 'error-message',
425
+ t('account.anonymous_order.not_found.description'),
426
+ currentBreakpoint
427
+ );
428
+ const resetButtonText = readProperty(
429
+ props,
430
+ 'reset-button-text',
431
+ locale === 'tr' ? 'Yeni Arama Yap' : 'Search Another Order',
432
+ currentBreakpoint
433
+ );
434
+ const trackShipmentText = readProperty(
435
+ props,
436
+ 'track-shipment-text',
437
+ t('account.my_orders.detail.track_shipment'),
438
+ currentBreakpoint
439
+ );
440
+ const showStatusBadge = readBoolean(
441
+ props,
442
+ 'show-status-badge',
443
+ true,
444
+ currentBreakpoint
445
+ );
446
+ const showTrackingCard = readBoolean(
447
+ props,
448
+ 'show-tracking-card',
449
+ true,
450
+ currentBreakpoint
451
+ );
452
+ const showOrderItems = readBoolean(
453
+ props,
454
+ 'show-order-items',
455
+ true,
456
+ currentBreakpoint
457
+ );
458
+ const showShippingAddress = readBoolean(
459
+ props,
460
+ 'show-shipping-address',
461
+ true,
462
+ currentBreakpoint
463
+ );
464
+ const maxItems = Math.max(
465
+ 1,
466
+ Math.min(6, readNumber(props, 'max-items', 3, currentBreakpoint))
467
+ );
468
+
469
+ const requiredMessage = t('account.anonymous_order.error.required');
470
+ const emailMessage = t('account.anonymous_order.error.email_valid');
471
+
472
+ const schema = useMemo(
473
+ () =>
474
+ yup.object().shape({
475
+ number: yup.string().trim().required(requiredMessage),
476
+ email: yup.string().trim().email(emailMessage).required(requiredMessage)
477
+ }),
478
+ [emailMessage, requiredMessage]
479
+ );
480
+
481
+ const {
482
+ register,
483
+ handleSubmit,
484
+ formState: { errors }
485
+ } = useForm<OrderTrackingLookupFormType>({
486
+ resolver: yupResolver(schema)
487
+ });
488
+
489
+ const maxWidth = getResponsiveValue(
490
+ section.styles?.['max-width'],
491
+ currentBreakpoint,
492
+ 'normal'
493
+ );
494
+ const maxWidthClass =
495
+ maxWidth === 'narrow'
496
+ ? 'max-w-4xl'
497
+ : maxWidth === 'normal'
498
+ ? 'max-w-7xl'
499
+ : maxWidth === 'full'
500
+ ? 'w-full'
501
+ : '';
502
+ const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
503
+
504
+ const inputBg = resolveThemeCssVariables(
505
+ String(
506
+ getResponsiveValue(
507
+ section.styles?.['input-background-color'],
508
+ currentBreakpoint,
509
+ '#ffffff'
510
+ )
511
+ ),
512
+ themeSettings
513
+ );
514
+ const inputTextColor = resolveThemeCssVariables(
515
+ String(
516
+ getResponsiveValue(
517
+ section.styles?.['input-text-color'],
518
+ currentBreakpoint,
519
+ '#0f172a'
520
+ )
521
+ ),
522
+ themeSettings
523
+ );
524
+ const inputBorderColor = resolveThemeCssVariables(
525
+ String(
526
+ getResponsiveValue(
527
+ section.styles?.['input-border-color'],
528
+ currentBreakpoint,
529
+ '#cbd5e1'
530
+ )
531
+ ),
532
+ themeSettings
533
+ );
534
+ const inputBorderRadius = String(
535
+ getResponsiveValue(
536
+ section.styles?.['input-border-radius'],
537
+ currentBreakpoint,
538
+ '10px'
539
+ )
540
+ );
541
+ const inputPadding = String(
542
+ getResponsiveValue(
543
+ section.styles?.['input-padding'],
544
+ currentBreakpoint,
545
+ '14px 16px'
546
+ )
547
+ );
548
+ const buttonBg = resolveThemeCssVariables(
549
+ String(
550
+ getResponsiveValue(
551
+ section.styles?.['button-background-color'],
552
+ currentBreakpoint,
553
+ '#0f172a'
554
+ )
555
+ ),
556
+ themeSettings
557
+ );
558
+ const buttonTextColor = resolveThemeCssVariables(
559
+ String(
560
+ getResponsiveValue(
561
+ section.styles?.['button-text-color'],
562
+ currentBreakpoint,
563
+ '#ffffff'
564
+ )
565
+ ),
566
+ themeSettings
567
+ );
568
+ const buttonBorderRadius = String(
569
+ getResponsiveValue(
570
+ section.styles?.['button-border-radius'],
571
+ currentBreakpoint,
572
+ '10px'
573
+ )
574
+ );
575
+ const buttonPadding = String(
576
+ getResponsiveValue(
577
+ section.styles?.['button-padding'],
578
+ currentBreakpoint,
579
+ '14px 28px'
580
+ )
581
+ );
582
+ const resultCardBackground = resolveThemeCssVariables(
583
+ String(
584
+ getResponsiveValue(
585
+ section.styles?.['result-card-background-color'],
586
+ currentBreakpoint,
587
+ '#ffffff'
588
+ )
589
+ ),
590
+ themeSettings
591
+ );
592
+ const resultCardBorderColor = resolveThemeCssVariables(
593
+ String(
594
+ getResponsiveValue(
595
+ section.styles?.['result-card-border-color'],
596
+ currentBreakpoint,
597
+ '#e2e8f0'
598
+ )
599
+ ),
600
+ themeSettings
601
+ );
602
+ const resultCardBorderRadius = String(
603
+ getResponsiveValue(
604
+ section.styles?.['result-card-border-radius'],
605
+ currentBreakpoint,
606
+ '16px'
607
+ )
608
+ );
609
+ const formGap = getResponsiveValue(
610
+ section.styles?.['form-gap'],
611
+ currentBreakpoint,
612
+ 16
613
+ );
614
+ const badgeBackground = resolveThemeCssVariables(
615
+ String(
616
+ getResponsiveValue(
617
+ section.styles?.['badge-background-color'],
618
+ currentBreakpoint,
619
+ '#dbeafe'
620
+ )
621
+ ),
622
+ themeSettings
623
+ );
624
+ const badgeText = resolveThemeCssVariables(
625
+ String(
626
+ getResponsiveValue(
627
+ section.styles?.['badge-text-color'],
628
+ currentBreakpoint,
629
+ '#1d4ed8'
630
+ )
631
+ ),
632
+ themeSettings
633
+ );
634
+
635
+ const filteredStyles = Object.fromEntries(
636
+ Object.entries(section.styles || {}).filter(
637
+ ([key]) =>
638
+ ![
639
+ 'max-width',
640
+ 'form-gap',
641
+ 'input-background-color',
642
+ 'input-text-color',
643
+ 'input-border-color',
644
+ 'input-border-radius',
645
+ 'input-padding',
646
+ 'button-background-color',
647
+ 'button-text-color',
648
+ 'button-hover-background-color',
649
+ 'button-border-radius',
650
+ 'button-padding',
651
+ 'result-card-background-color',
652
+ 'result-card-border-color',
653
+ 'result-card-border-radius',
654
+ 'badge-background-color',
655
+ 'badge-text-color'
656
+ ].includes(key)
657
+ )
658
+ );
659
+
660
+ const sectionStyles = getCSSStyles(
661
+ filteredStyles,
662
+ themeSettings,
663
+ currentBreakpoint
664
+ );
665
+
666
+ const sortedBlocks = [...(section.blocks || [])]
667
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
668
+ .filter((block) => (isDesigner ? true : !block.hidden));
669
+
670
+ const contentGroup = sortedBlocks.find(
671
+ (block) => block.type === 'group' && block.label === 'Content Container'
672
+ );
673
+ const textBlocks =
674
+ contentGroup?.blocks?.filter((block) => block.type === 'text') || [];
675
+ const formPanelWrapperBlock = findOrderTrackingRoleBlock(
676
+ sortedBlocks,
677
+ 'form-panel-wrapper',
678
+ currentBreakpoint
679
+ );
680
+ const orderInputBlock = findOrderTrackingRoleBlock(
681
+ sortedBlocks,
682
+ 'order-input',
683
+ currentBreakpoint
684
+ );
685
+ const emailInputBlock = findOrderTrackingRoleBlock(
686
+ sortedBlocks,
687
+ 'email-input',
688
+ currentBreakpoint
689
+ );
690
+ const submitButtonBlock = findOrderTrackingRoleBlock(
691
+ sortedBlocks,
692
+ 'submit-button',
693
+ currentBreakpoint
694
+ );
695
+ const helperTextBlock = findOrderTrackingRoleBlock(
696
+ sortedBlocks,
697
+ 'helper-text',
698
+ currentBreakpoint
699
+ );
700
+ const errorWrapperBlock = findOrderTrackingRoleBlock(
701
+ sortedBlocks,
702
+ 'error-wrapper',
703
+ currentBreakpoint
704
+ );
705
+ const errorTextBlock = findOrderTrackingRoleBlock(
706
+ sortedBlocks,
707
+ 'error-text',
708
+ currentBreakpoint
709
+ );
710
+ const resultWrapperBlock = findOrderTrackingRoleBlock(
711
+ sortedBlocks,
712
+ 'result-wrapper',
713
+ currentBreakpoint
714
+ );
715
+ const statusBadgeBlock = findOrderTrackingRoleBlock(
716
+ sortedBlocks,
717
+ 'status-badge',
718
+ currentBreakpoint
719
+ );
720
+ const summaryCardWrapperBlock = findOrderTrackingRoleBlock(
721
+ sortedBlocks,
722
+ 'summary-card-wrapper',
723
+ currentBreakpoint
724
+ );
725
+ const trackingCardWrapperBlock = findOrderTrackingRoleBlock(
726
+ sortedBlocks,
727
+ 'tracking-card-wrapper',
728
+ currentBreakpoint
729
+ );
730
+ const shippingAddressWrapperBlock = findOrderTrackingRoleBlock(
731
+ sortedBlocks,
732
+ 'shipping-address-wrapper',
733
+ currentBreakpoint
734
+ );
735
+ const itemsListWrapperBlock = findOrderTrackingRoleBlock(
736
+ sortedBlocks,
737
+ 'items-list-wrapper',
738
+ currentBreakpoint
739
+ );
740
+ const orderItemWrapperBlock = findOrderTrackingRoleBlock(
741
+ sortedBlocks,
742
+ 'order-item-wrapper',
743
+ currentBreakpoint
744
+ );
745
+ const resetButtonBlock = findOrderTrackingRoleBlock(
746
+ sortedBlocks,
747
+ 'reset-button',
748
+ currentBreakpoint
749
+ );
750
+
751
+ const postBlockAction = (type: string, blockId: string, label?: string) => {
752
+ if (!window.parent) return;
753
+ window.parent.postMessage(
754
+ {
755
+ type,
756
+ data: {
757
+ placeholderId,
758
+ sectionId: section.id,
759
+ blockId,
760
+ ...(label ? { label } : {})
761
+ }
762
+ },
763
+ '*'
764
+ );
765
+ };
766
+
767
+ const renderBlock = (block: Block, keyOverride?: React.Key) => {
768
+ const actionBlockId = block.styleSourceId || block.id;
769
+
770
+ return (
771
+ <ThemeBlock
772
+ key={keyOverride || block.id}
773
+ block={block}
774
+ placeholderId={placeholderId}
775
+ sectionId={section.id}
776
+ isDesigner={isDesigner}
777
+ isSelected={
778
+ selectedBlockId === actionBlockId || selectedBlockId === block.id
779
+ }
780
+ selectedBlockId={selectedBlockId}
781
+ currentBreakpoint={currentBreakpoint}
782
+ onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
783
+ onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
784
+ onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
785
+ onToggleVisibility={() =>
786
+ postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
787
+ }
788
+ onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
789
+ onRename={(newLabel) =>
790
+ postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
791
+ }
792
+ />
793
+ );
794
+ };
795
+
796
+ const renderSelectableWrapper = (
797
+ block: Block | null,
798
+ children: React.ReactNode,
799
+ options: {
800
+ className?: string;
801
+ style?: React.CSSProperties;
802
+ keyOverride?: string;
803
+ } = {}
804
+ ) => {
805
+ if (!block) {
806
+ return (
807
+ <div
808
+ key={options.keyOverride}
809
+ className={options.className}
810
+ style={options.style}
811
+ >
812
+ {children}
813
+ </div>
814
+ );
815
+ }
816
+
817
+ const actionBlockId = block.styleSourceId || block.id;
818
+ const wrapperStyle = {
819
+ ...(options.style || {}),
820
+ ...getCSSStyles(block.styles || {}, themeSettings, currentBreakpoint)
821
+ } as React.CSSProperties;
822
+
823
+ return (
824
+ <WithDesignerFeatures
825
+ key={options.keyOverride || block.id}
826
+ block={block}
827
+ placeholderId={placeholderId}
828
+ sectionId={section.id}
829
+ isDesigner={isDesigner}
830
+ isSelected={
831
+ selectedBlockId === actionBlockId || selectedBlockId === block.id
832
+ }
833
+ currentBreakpoint={currentBreakpoint}
834
+ className={options.className}
835
+ style={wrapperStyle}
836
+ onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
837
+ onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
838
+ onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
839
+ onToggleVisibility={() =>
840
+ postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
841
+ }
842
+ onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
843
+ onRename={(newLabel) =>
844
+ postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
845
+ }
846
+ >
847
+ {children}
848
+ </WithDesignerFeatures>
849
+ );
850
+ };
851
+
852
+ const renderSelectableElement = (
853
+ block: Block | null,
854
+ children: React.ReactNode,
855
+ options: {
856
+ wrapperClassName?: string;
857
+ wrapperStyle?: React.CSSProperties;
858
+ keyOverride?: string;
859
+ } = {}
860
+ ) => {
861
+ if (!block) {
862
+ return (
863
+ <div
864
+ key={options.keyOverride}
865
+ className={options.wrapperClassName}
866
+ style={options.wrapperStyle}
867
+ >
868
+ {children}
869
+ </div>
870
+ );
871
+ }
872
+
873
+ const actionBlockId = block.styleSourceId || block.id;
874
+
875
+ return (
876
+ <WithDesignerFeatures
877
+ key={options.keyOverride || block.id}
878
+ block={block}
879
+ placeholderId={placeholderId}
880
+ sectionId={section.id}
881
+ isDesigner={isDesigner}
882
+ isSelected={
883
+ selectedBlockId === actionBlockId || selectedBlockId === block.id
884
+ }
885
+ currentBreakpoint={currentBreakpoint}
886
+ className={options.wrapperClassName}
887
+ style={options.wrapperStyle}
888
+ onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
889
+ onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
890
+ onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
891
+ onToggleVisibility={() =>
892
+ postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
893
+ }
894
+ onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
895
+ onRename={(newLabel) =>
896
+ postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
897
+ }
898
+ >
899
+ {children}
900
+ </WithDesignerFeatures>
901
+ );
902
+ };
903
+
904
+ const statusLabel = lookupOrder?.status?.label || '';
905
+ const statusTone = getStatusTone(statusLabel);
906
+
907
+ const inputStyle: React.CSSProperties = {
908
+ width: '100%',
909
+ backgroundColor: inputBg,
910
+ color: inputTextColor,
911
+ border: `1px solid ${inputBorderColor}`,
912
+ borderRadius: inputBorderRadius,
913
+ padding: inputPadding,
914
+ fontSize: '15px',
915
+ outline: 'none',
916
+ fontFamily: 'inherit'
917
+ };
918
+
919
+ const buttonStyle: React.CSSProperties = {
920
+ width: '100%',
921
+ backgroundColor: buttonBg,
922
+ color: buttonTextColor,
923
+ border: 'none',
924
+ borderRadius: buttonBorderRadius,
925
+ padding: buttonPadding,
926
+ fontSize: '14px',
927
+ fontWeight: 700,
928
+ cursor: isSubmitting ? 'default' : 'pointer',
929
+ opacity: isSubmitting ? 0.7 : 1,
930
+ transition: 'all 0.2s ease',
931
+ fontFamily: 'inherit',
932
+ whiteSpace: 'nowrap',
933
+ lineHeight: 1.2
934
+ };
935
+
936
+ const panelStyle: React.CSSProperties = {
937
+ backgroundColor: resultCardBackground,
938
+ border: `1px solid ${resultCardBorderColor}`,
939
+ borderRadius: resultCardBorderRadius
940
+ };
941
+ const mergeControlStyles = (
942
+ baseStyle: React.CSSProperties,
943
+ block: Block | null
944
+ ): React.CSSProperties => ({
945
+ ...baseStyle,
946
+ ...(block
947
+ ? getCSSStyles(
948
+ Object.fromEntries(
949
+ Object.entries(block.styles || {}).filter(
950
+ ([styleKey]) => !CONTROL_LAYOUT_STYLE_KEYS.has(styleKey)
951
+ )
952
+ ),
953
+ themeSettings,
954
+ currentBreakpoint
955
+ )
956
+ : {})
957
+ });
958
+ const orderInputStyle = mergeControlStyles(inputStyle, orderInputBlock);
959
+ const emailInputStyle = mergeControlStyles(inputStyle, emailInputBlock);
960
+ const submitButtonStyle = mergeControlStyles(buttonStyle, submitButtonBlock);
961
+ const statusBadgeStyle: React.CSSProperties = {
962
+ backgroundColor: badgeBackground || statusTone.background,
963
+ color: badgeText || statusTone.color,
964
+ ...(statusBadgeBlock
965
+ ? getCSSStyles(
966
+ statusBadgeBlock.styles || {},
967
+ themeSettings,
968
+ currentBreakpoint
969
+ )
970
+ : {})
971
+ };
972
+ const resetButtonStyle = mergeControlStyles(
973
+ {
974
+ display: 'inline-flex',
975
+ alignItems: 'center',
976
+ justifyContent: 'center',
977
+ border: '1px solid #cbd5e1',
978
+ backgroundColor: '#ffffff',
979
+ color: '#0f172a',
980
+ borderRadius: '10px',
981
+ padding: '12px 20px',
982
+ fontSize: '14px',
983
+ fontWeight: 700,
984
+ fontFamily: 'inherit',
985
+ cursor: 'pointer',
986
+ whiteSpace: 'nowrap',
987
+ lineHeight: 1.2
988
+ },
989
+ resetButtonBlock
990
+ );
991
+
992
+ const onSubmit: SubmitHandler<OrderTrackingLookupFormType> = async (data) => {
993
+ setLookupError('');
994
+ setLookupOrder(null);
995
+ setIsSubmitting(true);
996
+
997
+ let latestErrorMessage = errorMessage;
998
+
999
+ try {
1000
+ const response = await getAnonymousTracking(data).unwrap();
1001
+ const normalizedOrder = normalizeOrderResponse(response);
1002
+
1003
+ if (normalizedOrder) {
1004
+ setLookupOrder(normalizedOrder);
1005
+ setIsSubmitting(false);
1006
+ return;
1007
+ }
1008
+ } catch (error) {
1009
+ latestErrorMessage = extractErrorMessage(error, errorMessage);
1010
+ }
1011
+
1012
+ try {
1013
+ const authenticatedOrder = await findAuthenticatedOrder(data.number);
1014
+ if (authenticatedOrder) {
1015
+ setLookupOrder(authenticatedOrder);
1016
+ return;
1017
+ }
1018
+ } catch {
1019
+ // Ignore authenticated lookup failures and fall back to the latest API error.
1020
+ } finally {
1021
+ setIsSubmitting(false);
1022
+ }
1023
+
1024
+ setLookupError(latestErrorMessage);
1025
+ };
1026
+
1027
+ const handleReset = () => {
1028
+ setLookupOrder(null);
1029
+ setLookupError('');
1030
+ };
1031
+
1032
+ const visibleItems = (lookupOrder?.orderitem_set || []).slice(0, maxItems);
1033
+ const groupedPackageCount = lookupOrder
1034
+ ? new Set(
1035
+ lookupOrder.orderitem_set
1036
+ .map((item) => (item as OrderItem & { tracking_number?: string }).tracking_number)
1037
+ .filter(Boolean)
1038
+ ).size || 1
1039
+ : 0;
1040
+ const trackingUrl = resolveTrackingUrl(lookupOrder);
1041
+ const trackingNumber = resolveTrackingNumber(lookupOrder);
1042
+ const localeCode = locale || 'en';
1043
+ const isMobile = currentBreakpoint === 'mobile';
1044
+
1045
+ return (
1046
+ <div
1047
+ className={twMerge(
1048
+ clsx('w-full relative z-10', hasMaxWidth && 'mx-auto', maxWidthClass)
1049
+ )}
1050
+ style={sectionStyles}
1051
+ >
1052
+ <div
1053
+ className={clsx(
1054
+ 'grid items-start gap-6',
1055
+ isMobile ? 'grid-cols-1' : 'md:grid-cols-[0.9fr_1.1fr]'
1056
+ )}
1057
+ >
1058
+ <div className="flex flex-col gap-4">
1059
+ {textBlocks.map(renderBlock)}
1060
+ </div>
1061
+
1062
+ <div className="flex flex-col gap-4" style={{ gap: `${formGap}px` }}>
1063
+ {renderSelectableWrapper(
1064
+ formPanelWrapperBlock,
1065
+ <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-3">
1066
+ {renderSelectableElement(
1067
+ orderInputBlock,
1068
+ <input
1069
+ type="text"
1070
+ placeholder={orderNumberPlaceholder}
1071
+ {...register('number')}
1072
+ style={orderInputStyle}
1073
+ />,
1074
+ {
1075
+ keyOverride: 'order-input'
1076
+ }
1077
+ )}
1078
+ {errors.number && (
1079
+ <span className="text-xs text-[#ef4444]">
1080
+ {errors.number.message}
1081
+ </span>
1082
+ )}
1083
+
1084
+ {renderSelectableElement(
1085
+ emailInputBlock,
1086
+ <input
1087
+ type="email"
1088
+ placeholder={emailPlaceholder}
1089
+ {...register('email')}
1090
+ style={emailInputStyle}
1091
+ />,
1092
+ {
1093
+ keyOverride: 'email-input'
1094
+ }
1095
+ )}
1096
+ {errors.email && (
1097
+ <span className="text-xs text-[#ef4444]">
1098
+ {errors.email.message}
1099
+ </span>
1100
+ )}
1101
+
1102
+ {renderSelectableElement(
1103
+ submitButtonBlock,
1104
+ <button
1105
+ type="submit"
1106
+ disabled={isSubmitting}
1107
+ style={submitButtonStyle}
1108
+ >
1109
+ {isSubmitting
1110
+ ? localeCode === 'tr'
1111
+ ? 'Yukleniyor...'
1112
+ : 'Loading...'
1113
+ : buttonText}
1114
+ </button>,
1115
+ {
1116
+ keyOverride: 'submit-button'
1117
+ }
1118
+ )}
1119
+
1120
+ {helperTextBlock ? (
1121
+ renderBlock(
1122
+ cloneOrderTrackingBlock(helperTextBlock, {
1123
+ value: toHtmlText(helperText)
1124
+ }),
1125
+ 'helper-text'
1126
+ )
1127
+ ) : (
1128
+ <p className="m-0 text-sm leading-6 text-[#64748b]">
1129
+ {helperText}
1130
+ </p>
1131
+ )}
1132
+ </form>,
1133
+ {
1134
+ className: 'p-5 md:p-6',
1135
+ style: panelStyle,
1136
+ keyOverride: 'form-panel-wrapper'
1137
+ }
1138
+ )}
1139
+
1140
+ {lookupError ? (
1141
+ renderSelectableWrapper(
1142
+ errorWrapperBlock,
1143
+ errorTextBlock ? (
1144
+ renderBlock(
1145
+ cloneOrderTrackingBlock(errorTextBlock, {
1146
+ value: toHtmlText(lookupError)
1147
+ }),
1148
+ 'error-text'
1149
+ )
1150
+ ) : (
1151
+ <div className="text-sm text-[#b91c1c]">{lookupError}</div>
1152
+ ),
1153
+ {
1154
+ className:
1155
+ 'rounded-[12px] border border-[#fecaca] bg-[#fff1f2] px-4 py-3',
1156
+ keyOverride: 'lookup-error'
1157
+ }
1158
+ )
1159
+ ) : null}
1160
+ </div>
1161
+ </div>
1162
+
1163
+ {lookupOrder ? (
1164
+ renderSelectableWrapper(
1165
+ resultWrapperBlock,
1166
+ <div className="flex flex-col gap-4">
1167
+ <div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
1168
+ <div className="flex flex-col gap-1">
1169
+ <span className="text-xs font-semibold uppercase tracking-[0.12em] text-[#64748b]">
1170
+ {t('account.my_orders.detail.title')}
1171
+ </span>
1172
+ <h3 className="m-0 text-2xl font-bold text-[#0f172a]">
1173
+ #{lookupOrder.number}
1174
+ </h3>
1175
+ </div>
1176
+
1177
+ {showStatusBadge && statusLabel ? (
1178
+ renderSelectableElement(
1179
+ statusBadgeBlock,
1180
+ <span
1181
+ className="inline-flex w-fit items-center px-3 py-2 text-xs font-semibold uppercase tracking-[0.08em]"
1182
+ style={statusBadgeStyle}
1183
+ >
1184
+ {statusLabel}
1185
+ </span>,
1186
+ {
1187
+ keyOverride: 'status-badge'
1188
+ }
1189
+ )
1190
+ ) : null}
1191
+ </div>
1192
+
1193
+ <div
1194
+ className={clsx(
1195
+ 'grid gap-3',
1196
+ isMobile ? 'grid-cols-2' : 'md:grid-cols-4'
1197
+ )}
1198
+ >
1199
+ {[
1200
+ {
1201
+ key: 'summary-order-number',
1202
+ label: t('account.my_orders.detail.order_number'),
1203
+ value: `#${lookupOrder.number}`
1204
+ },
1205
+ {
1206
+ key: 'summary-order-date',
1207
+ label: t('account.my_orders.detail.order_date'),
1208
+ value: formatDate(lookupOrder.created_date, localeCode)
1209
+ },
1210
+ {
1211
+ key: 'summary-order-total',
1212
+ label: t('account.my_orders.detail.total'),
1213
+ value: formatAmount(lookupOrder.amount, lookupOrder.currency)
1214
+ },
1215
+ {
1216
+ key: 'summary-order-products',
1217
+ label: t('account.my_orders.detail.products'),
1218
+ value: `${lookupOrder.orderitem_set?.length || 0} / ${groupedPackageCount} ${t(
1219
+ 'account.my_orders.detail.packages'
1220
+ )}`
1221
+ }
1222
+ ].map((summaryItem) =>
1223
+ renderSelectableWrapper(
1224
+ summaryCardWrapperBlock,
1225
+ <>
1226
+ <div className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#64748b]">
1227
+ {summaryItem.label}
1228
+ </div>
1229
+ <div className="mt-2 text-sm font-semibold text-[#0f172a]">
1230
+ {summaryItem.value}
1231
+ </div>
1232
+ </>,
1233
+ {
1234
+ className: 'rounded-[10px] bg-[#f8fafc] p-3',
1235
+ keyOverride: summaryItem.key
1236
+ }
1237
+ )
1238
+ )}
1239
+ </div>
1240
+
1241
+ {showTrackingCard && (trackingUrl || trackingNumber) ? (
1242
+ renderSelectableWrapper(
1243
+ trackingCardWrapperBlock,
1244
+ <>
1245
+ <div className="flex flex-col gap-1">
1246
+ <span className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#1d4ed8]">
1247
+ {localeCode === 'tr' ? 'Kargo Bilgisi' : 'Shipment Details'}
1248
+ </span>
1249
+ <span className="text-sm font-semibold text-[#0f172a]">
1250
+ {trackingNumber || statusLabel}
1251
+ </span>
1252
+ </div>
1253
+
1254
+ {trackingUrl ? (
1255
+ <a
1256
+ href={trackingUrl}
1257
+ target="_blank"
1258
+ rel="noreferrer"
1259
+ className="inline-flex w-fit items-center justify-center rounded-[10px] bg-[#0f172a] px-5 py-3 text-sm font-semibold text-white no-underline"
1260
+ >
1261
+ {trackShipmentText}
1262
+ </a>
1263
+ ) : null}
1264
+ </>,
1265
+ {
1266
+ className:
1267
+ 'flex flex-col gap-3 rounded-[12px] border border-[#dbeafe] bg-[#eff6ff] p-4 md:flex-row md:items-center md:justify-between',
1268
+ keyOverride: 'tracking-card'
1269
+ }
1270
+ )
1271
+ ) : null}
1272
+
1273
+ {showShippingAddress && lookupOrder.shipping_address?.line ? (
1274
+ renderSelectableWrapper(
1275
+ shippingAddressWrapperBlock,
1276
+ <>
1277
+ <div className="mb-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[#64748b]">
1278
+ {t('account.my_orders.detail.delivery_address')}
1279
+ </div>
1280
+ <div>
1281
+ {lookupOrder.shipping_address.line}{' '}
1282
+ {lookupOrder.shipping_address.district?.name}{' '}
1283
+ {lookupOrder.shipping_address.township?.name}{' '}
1284
+ {lookupOrder.shipping_address.city?.name}
1285
+ </div>
1286
+ </>,
1287
+ {
1288
+ className: 'rounded-[12px] bg-[#f8fafc] p-4 text-sm leading-6 text-[#334155]',
1289
+ keyOverride: 'shipping-address'
1290
+ }
1291
+ )
1292
+ ) : null}
1293
+
1294
+ {showOrderItems ? (
1295
+ <div className="flex flex-col gap-3">
1296
+ <div className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#64748b]">
1297
+ {t('account.my_orders.detail.products')}
1298
+ </div>
1299
+ {renderSelectableWrapper(
1300
+ itemsListWrapperBlock,
1301
+ visibleItems.map((item, index) =>
1302
+ renderSelectableWrapper(
1303
+ orderItemWrapperBlock,
1304
+ <>
1305
+ <div className="flex min-w-0 items-center gap-3">
1306
+ <div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-[8px] bg-[#f1f5f9]">
1307
+ <Image
1308
+ src={item.product?.image || '/noimage.jpg'}
1309
+ alt={item.product?.name || ''}
1310
+ fill
1311
+ sizes="64px"
1312
+ aspectRatio={1}
1313
+ imageClassName="object-cover"
1314
+ />
1315
+ </div>
1316
+ <div className="min-w-0">
1317
+ <div className="truncate text-sm font-semibold text-[#0f172a]">
1318
+ {item.product?.name}
1319
+ </div>
1320
+ <div className="mt-1 text-xs text-[#64748b]">
1321
+ {t('account.my_orders.detail.items')}: {item.quantity}
1322
+ {' · '}
1323
+ {item.status?.label}
1324
+ </div>
1325
+ </div>
1326
+ </div>
1327
+ <div className="flex shrink-0 flex-col items-start text-sm md:items-end">
1328
+ {parseFloat(item.retail_price) > parseFloat(item.price) ? (
1329
+ <span className="text-xs text-[#94a3b8] line-through">
1330
+ {formatAmount(item.retail_price, item.price_currency)}
1331
+ </span>
1332
+ ) : null}
1333
+ <span className="font-semibold text-[#0f172a]">
1334
+ {formatAmount(item.price, item.price_currency)}
1335
+ </span>
1336
+ </div>
1337
+ </>,
1338
+ {
1339
+ className:
1340
+ 'flex flex-col gap-3 p-4 md:flex-row md:items-center md:justify-between',
1341
+ keyOverride: `order-item-${item.id}-${index}`
1342
+ }
1343
+ )
1344
+ ),
1345
+ {
1346
+ className:
1347
+ 'flex flex-col divide-y divide-[#e2e8f0] overflow-hidden rounded-[12px] border border-[#e2e8f0] bg-white',
1348
+ keyOverride: 'items-list-wrapper'
1349
+ }
1350
+ )}
1351
+ </div>
1352
+ ) : null}
1353
+
1354
+ <div className="pt-2">
1355
+ {renderSelectableElement(
1356
+ resetButtonBlock,
1357
+ <button
1358
+ type="button"
1359
+ onClick={handleReset}
1360
+ style={resetButtonStyle}
1361
+ >
1362
+ {resetButtonText}
1363
+ </button>,
1364
+ {
1365
+ keyOverride: 'reset-button'
1366
+ }
1367
+ )}
1368
+ </div>
1369
+ </div>,
1370
+ {
1371
+ className: 'mt-6 flex flex-col gap-4 p-5 md:p-6',
1372
+ style: panelStyle,
1373
+ keyOverride: 'result-wrapper'
1374
+ }
1375
+ )
1376
+ ) : null}
1377
+ </div>
1378
+ );
1379
+ }