@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.
- package/.eslintrc.js +12 -0
- package/CHANGELOG.md +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +25 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +101 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- 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, '&')
|
|
97
|
+
.replace(/</g, '<')
|
|
98
|
+
.replace(/>/g, '>')
|
|
99
|
+
.replace(/"/g, '"')
|
|
100
|
+
.replace(/'/g, ''');
|
|
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
|
+
}
|