@akinon/projectzero 2.0.32-rc.0 → 2.0.33-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -6
- package/app-template/.env.example +0 -1
- package/app-template/AGENTS.md +0 -8
- package/app-template/CHANGELOG.md +66 -75
- package/app-template/README.md +1 -25
- package/app-template/next.config.mjs +1 -7
- package/app-template/package.json +41 -41
- package/app-template/public/locales/en/checkout.json +50 -0
- package/app-template/public/locales/tr/checkout.json +50 -0
- package/app-template/src/app/[pz]/basket/page.tsx +33 -6
- package/app-template/src/app/[pz]/orders/checkout/page.tsx +57 -92
- package/app-template/src/app/api/theme-settings/route.ts +21 -2
- package/app-template/src/data/server/theme.ts +6 -25
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +0 -1
- package/app-template/src/proxy.ts +1 -2
- package/app-template/src/views/checkout/variants/basket-and-checkout/basket-section.tsx +149 -0
- package/app-template/src/views/checkout/variants/basket-and-checkout/index.tsx +132 -0
- package/app-template/src/views/checkout/variants/multi-step/index.tsx +73 -0
- package/app-template/src/views/checkout/variants/one-page/accordion-section.tsx +140 -0
- package/app-template/src/views/checkout/variants/one-page/hooks/use-checkout-funnel-gtm.ts +60 -0
- package/app-template/src/views/checkout/variants/one-page/hooks/use-section-state.ts +99 -0
- package/app-template/src/views/checkout/variants/one-page/index.tsx +125 -0
- package/app-template/src/views/checkout/variants/one-page/mobile-summary-sheet.tsx +109 -0
- package/app-template/src/views/checkout/variants/one-page/one-page-summary.tsx +237 -0
- package/app-template/src/views/checkout/variants/one-page/payment-options-grid.tsx +201 -0
- package/app-template/src/views/checkout/variants/one-page/sections/address-section.tsx +318 -0
- package/app-template/src/views/checkout/variants/one-page/sections/contact-section.tsx +62 -0
- package/app-template/src/views/checkout/variants/one-page/sections/payment-section.tsx +71 -0
- package/app-template/src/views/checkout/variants/one-page/sections/shipping-section.tsx +298 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/address-skeleton.tsx +25 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/checkout-skeleton.tsx +27 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/index.ts +6 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/payment-skeleton.tsx +23 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/section-skeleton.tsx +47 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/shipping-skeleton.tsx +20 -0
- package/app-template/src/views/checkout/variants/one-page/skeletons/summary-skeleton.tsx +38 -0
- package/app-template/src/views/checkout/variants/one-page/trust-badges.tsx +30 -0
- package/app-template/src/views/checkout/variants/registry.ts +32 -0
- package/app-template/src/views/checkout/variants/types.ts +30 -0
- package/app-template/src/views/checkout/variants/use-resolved-config.ts +89 -0
- package/app-template/src/views/header/search/index.tsx +5 -13
- package/app-template/src/views/product/slider.tsx +38 -85
- package/commands/plugins.ts +0 -4
- package/dist/commands/plugins.js +0 -4
- package/package.json +1 -1
- package/app-template/src/app/global-error.tsx +0 -22
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { useAppSelector } from '@akinon/next/redux/hooks';
|
|
2
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
3
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
4
|
+
import { Price, Link } from '@theme/components';
|
|
5
|
+
import { Image } from '@akinon/next/components/image';
|
|
6
|
+
import { StoreCredits } from '@theme/views/checkout/steps/payment/options/store-credit';
|
|
7
|
+
import type { RootState } from '@theme/redux/store';
|
|
8
|
+
import type { SectionId } from './hooks/use-section-state';
|
|
9
|
+
|
|
10
|
+
interface OnePageSummaryProps {
|
|
11
|
+
onEditSection: (section: SectionId) => void;
|
|
12
|
+
showReview?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const OnePageSummary = ({
|
|
16
|
+
onEditSection,
|
|
17
|
+
showReview = true
|
|
18
|
+
}: OnePageSummaryProps) => {
|
|
19
|
+
const { t } = useLocalization();
|
|
20
|
+
const preOrder = useAppSelector(
|
|
21
|
+
(state: RootState) => state.checkout.preOrder
|
|
22
|
+
);
|
|
23
|
+
const { isMobileApp } = useAppSelector((state: RootState) => state.root);
|
|
24
|
+
|
|
25
|
+
if (!preOrder?.basket) return null;
|
|
26
|
+
|
|
27
|
+
const itemCount = preOrder.basket.basketitem_set.length;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex flex-col gap-4">
|
|
31
|
+
<PluginModule
|
|
32
|
+
component={Component.CheckoutGiftPack}
|
|
33
|
+
props={{
|
|
34
|
+
className:
|
|
35
|
+
'flex flex-col w-full rounded-2xl bg-white shadow-[0_1px_3px_rgba(0,0,0,0.04)] ring-1 ring-black/[0.04]'
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
<StoreCredits />
|
|
39
|
+
|
|
40
|
+
<section className="overflow-hidden rounded-2xl bg-white shadow-[0_1px_3px_rgba(0,0,0,0.04)] ring-1 ring-black/[0.04]">
|
|
41
|
+
<header className="flex items-baseline justify-between px-6 py-5">
|
|
42
|
+
<span className="text-lg font-semibold tracking-tight text-black-800">
|
|
43
|
+
{t('checkout.summary.title')}
|
|
44
|
+
</span>
|
|
45
|
+
<span className="text-[11px] font-medium uppercase tracking-wider text-gray-500">
|
|
46
|
+
{itemCount} {t('checkout.summary.items')}
|
|
47
|
+
</span>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
<ul className="max-h-72 divide-y divide-gray-100 overflow-y-auto border-t border-gray-100">
|
|
51
|
+
{preOrder.basket.basketitem_set.map((item, index) => (
|
|
52
|
+
<li
|
|
53
|
+
key={`one-page-summary-item-${index}`}
|
|
54
|
+
className="flex gap-3 px-6 py-4"
|
|
55
|
+
>
|
|
56
|
+
<Link
|
|
57
|
+
href={item.product.absolute_url || '#'}
|
|
58
|
+
passHref
|
|
59
|
+
className={
|
|
60
|
+
isMobileApp
|
|
61
|
+
? 'pointer-events-none flex-shrink-0'
|
|
62
|
+
: 'flex-shrink-0 overflow-hidden rounded-md'
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
<Image
|
|
66
|
+
src={item.product.productimage_set[0]?.image}
|
|
67
|
+
alt={item.product.name}
|
|
68
|
+
width={56}
|
|
69
|
+
height={84}
|
|
70
|
+
/>
|
|
71
|
+
</Link>
|
|
72
|
+
<div className="flex flex-1 flex-col justify-between text-xs">
|
|
73
|
+
<Link
|
|
74
|
+
href={item.product.absolute_url || '#'}
|
|
75
|
+
className="line-clamp-2 text-sm font-medium text-black-800 transition-colors hover:text-secondary"
|
|
76
|
+
>
|
|
77
|
+
{item.product.name}
|
|
78
|
+
</Link>
|
|
79
|
+
<div className="flex items-end justify-between gap-2">
|
|
80
|
+
<span className="text-gray-500">
|
|
81
|
+
{t('checkout.summary.quantity')}:{' '}
|
|
82
|
+
<span className="font-medium tabular-nums text-black-800">
|
|
83
|
+
{item.quantity}
|
|
84
|
+
</span>
|
|
85
|
+
</span>
|
|
86
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
87
|
+
{item.product.retail_price !== item.product.price && (
|
|
88
|
+
<span className="text-[10px] text-gray-400 line-through">
|
|
89
|
+
<Price value={item.product.retail_price} />
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
92
|
+
<span className="text-sm font-semibold text-black-800 tabular-nums">
|
|
93
|
+
<Price value={item.product.price} />
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
|
|
102
|
+
<dl className="space-y-2.5 border-t border-gray-100 px-6 py-5 text-sm text-gray-600">
|
|
103
|
+
<div className="flex items-center justify-between">
|
|
104
|
+
<dt>{t('checkout.summary.subtotal')}</dt>
|
|
105
|
+
<dd className="font-medium tabular-nums text-black-800">
|
|
106
|
+
<Price value={preOrder.basket.total_amount} />
|
|
107
|
+
</dd>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="flex items-center justify-between">
|
|
110
|
+
<dt>{t('checkout.summary.shipping')}</dt>
|
|
111
|
+
<dd className="font-medium tabular-nums text-black-800">
|
|
112
|
+
<Price value={preOrder.shipping_amount} />
|
|
113
|
+
</dd>
|
|
114
|
+
</div>
|
|
115
|
+
{parseFloat(preOrder.loyalty_money || '0') > 0 && (
|
|
116
|
+
<div className="flex items-center justify-between">
|
|
117
|
+
<dt>{t('checkout.summary.loyalty_money_total')}</dt>
|
|
118
|
+
<dd className="font-medium tabular-nums text-black-800">
|
|
119
|
+
<Price value={preOrder.loyalty_money} />
|
|
120
|
+
</dd>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
{parseFloat(preOrder.basket.total_discount_amount || '0') > 0 && (
|
|
124
|
+
<div className="flex items-center justify-between">
|
|
125
|
+
<dt>{t('checkout.summary.discounts_total')}</dt>
|
|
126
|
+
<dd className="font-medium tabular-nums text-black-800">
|
|
127
|
+
<Price
|
|
128
|
+
value={preOrder.basket.total_discount_amount}
|
|
129
|
+
useNegative
|
|
130
|
+
/>
|
|
131
|
+
</dd>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</dl>
|
|
135
|
+
|
|
136
|
+
<div className="flex items-baseline justify-between border-t border-gray-100 px-6 py-5">
|
|
137
|
+
<span className="text-sm font-medium uppercase tracking-wider text-gray-700">
|
|
138
|
+
{t('checkout.summary.total')}
|
|
139
|
+
</span>
|
|
140
|
+
<span className="text-2xl font-bold tracking-tight text-black-800 tabular-nums">
|
|
141
|
+
<Price value={preOrder.unpaid_amount} />
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
|
|
146
|
+
{showReview &&
|
|
147
|
+
(preOrder.shipping_address ||
|
|
148
|
+
preOrder.shipping_option ||
|
|
149
|
+
preOrder.payment_option) && (
|
|
150
|
+
<section className="overflow-hidden rounded-2xl bg-white shadow-[0_1px_3px_rgba(0,0,0,0.04)] ring-1 ring-black/[0.04]">
|
|
151
|
+
<header className="px-6 py-5">
|
|
152
|
+
<span className="text-base font-semibold tracking-tight text-black-800">
|
|
153
|
+
{t('checkout.one_page.summary.review_title')}
|
|
154
|
+
</span>
|
|
155
|
+
</header>
|
|
156
|
+
|
|
157
|
+
<ul className="divide-y divide-gray-100 border-t border-gray-100 text-sm text-black-800">
|
|
158
|
+
{preOrder.shipping_address && (
|
|
159
|
+
<li className="flex items-start justify-between gap-3 px-6 py-4">
|
|
160
|
+
<div className="flex-1">
|
|
161
|
+
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-500">
|
|
162
|
+
{t('checkout.one_page.summary.deliver_to')}
|
|
163
|
+
</div>
|
|
164
|
+
<div className="font-medium">
|
|
165
|
+
{preOrder.shipping_address.title}
|
|
166
|
+
</div>
|
|
167
|
+
<div className="text-xs text-gray-600">
|
|
168
|
+
{preOrder.shipping_address.line},{' '}
|
|
169
|
+
{preOrder.shipping_address.township?.name},{' '}
|
|
170
|
+
{preOrder.shipping_address.city?.name}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
onClick={() => onEditSection('address')}
|
|
176
|
+
className="rounded-md px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-black-800"
|
|
177
|
+
data-testid="one-page-summary-edit-address"
|
|
178
|
+
>
|
|
179
|
+
{t('checkout.one_page.edit')}
|
|
180
|
+
</button>
|
|
181
|
+
</li>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{preOrder.shipping_option && (
|
|
185
|
+
<li className="flex items-start justify-between gap-3 px-6 py-4">
|
|
186
|
+
<div className="flex-1">
|
|
187
|
+
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-500">
|
|
188
|
+
{t('checkout.one_page.summary.shipping_method')}
|
|
189
|
+
</div>
|
|
190
|
+
<div className="font-medium">
|
|
191
|
+
{preOrder.shipping_option.name}
|
|
192
|
+
</div>
|
|
193
|
+
{preOrder.shipping_option.shipping_amount && (
|
|
194
|
+
<div className="text-xs tabular-nums text-gray-600">
|
|
195
|
+
<Price value={preOrder.shipping_option.shipping_amount} />
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
onClick={() => onEditSection('shipping')}
|
|
202
|
+
className="rounded-md px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-black-800"
|
|
203
|
+
data-testid="one-page-summary-edit-shipping"
|
|
204
|
+
>
|
|
205
|
+
{t('checkout.one_page.edit')}
|
|
206
|
+
</button>
|
|
207
|
+
</li>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
{preOrder.payment_option && (
|
|
211
|
+
<li className="flex items-start justify-between gap-3 px-6 py-4">
|
|
212
|
+
<div className="flex-1">
|
|
213
|
+
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-500">
|
|
214
|
+
{t('checkout.one_page.summary.payment_method')}
|
|
215
|
+
</div>
|
|
216
|
+
<div className="font-medium">
|
|
217
|
+
{preOrder.payment_option.name}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
onClick={() => onEditSection('payment')}
|
|
223
|
+
className="rounded-md px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-black-800"
|
|
224
|
+
data-testid="one-page-summary-edit-payment"
|
|
225
|
+
>
|
|
226
|
+
{t('checkout.one_page.edit')}
|
|
227
|
+
</button>
|
|
228
|
+
</li>
|
|
229
|
+
)}
|
|
230
|
+
</ul>
|
|
231
|
+
</section>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export default OnePageSummary;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useAppSelector } from '@akinon/next/redux/hooks';
|
|
4
|
+
import { useSetPaymentOptionMutation } from '@akinon/next/data/client/checkout';
|
|
5
|
+
import { usePaymentOptions } from '@akinon/next/hooks/use-payment-options';
|
|
6
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
7
|
+
import { Icon } from '@theme/components';
|
|
8
|
+
import type { RootState } from '@theme/redux/store';
|
|
9
|
+
import type { CheckoutPaymentOption } from '@akinon/next/types';
|
|
10
|
+
|
|
11
|
+
const PAYMENT_TYPE_ICON: Record<string, string> = {
|
|
12
|
+
credit_card: 'cvc',
|
|
13
|
+
funds_transfer: 'file',
|
|
14
|
+
pay_on_delivery: 'address',
|
|
15
|
+
bkm_express: 'akinon',
|
|
16
|
+
masterpass: 'cvc',
|
|
17
|
+
masterpass_rest: 'cvc',
|
|
18
|
+
saved_card: 'cvc',
|
|
19
|
+
gpay: 'google',
|
|
20
|
+
google_pay: 'google',
|
|
21
|
+
apple_pay: 'apple',
|
|
22
|
+
wallet: 'akinon',
|
|
23
|
+
redirection: 'globe',
|
|
24
|
+
credit_payment: 'installment',
|
|
25
|
+
loyalty: 'heart-full',
|
|
26
|
+
hepsipay: 'akinon',
|
|
27
|
+
tabby: 'installment',
|
|
28
|
+
tamara: 'installment',
|
|
29
|
+
iyzico: 'cvc',
|
|
30
|
+
cybersource_uc: 'cvc',
|
|
31
|
+
flow_payment: 'installment'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const SLUG_KEYWORD_ICON: Array<[string, string]> = [
|
|
35
|
+
['apple', 'apple'],
|
|
36
|
+
['google', 'google'],
|
|
37
|
+
['gpay', 'google'],
|
|
38
|
+
['hepsi', 'akinon'],
|
|
39
|
+
['tabby', 'installment'],
|
|
40
|
+
['tamara', 'installment'],
|
|
41
|
+
['flow', 'installment'],
|
|
42
|
+
['saved', 'cvc'],
|
|
43
|
+
['credit', 'cvc'],
|
|
44
|
+
['transfer', 'file'],
|
|
45
|
+
['delivery', 'address'],
|
|
46
|
+
['wallet', 'akinon']
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const getOptionIcon = (option: CheckoutPaymentOption): string => {
|
|
50
|
+
if (PAYMENT_TYPE_ICON[option.payment_type]) {
|
|
51
|
+
return PAYMENT_TYPE_ICON[option.payment_type];
|
|
52
|
+
}
|
|
53
|
+
const slug = option.slug?.toLowerCase() || '';
|
|
54
|
+
for (const [keyword, icon] of SLUG_KEYWORD_ICON) {
|
|
55
|
+
if (slug.includes(keyword)) return icon;
|
|
56
|
+
}
|
|
57
|
+
return 'default';
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const PaymentOptionsGrid = () => {
|
|
61
|
+
const { t } = useLocalization();
|
|
62
|
+
const { preOrder, attributeBasedShippingOptions } = useAppSelector(
|
|
63
|
+
(state: RootState) => state.checkout
|
|
64
|
+
);
|
|
65
|
+
const [setPaymentOption, { isLoading }] = useSetPaymentOptionMutation();
|
|
66
|
+
const { filteredPaymentOptions } = usePaymentOptions();
|
|
67
|
+
|
|
68
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
69
|
+
|
|
70
|
+
const visibleOptions = useMemo(() => {
|
|
71
|
+
if (
|
|
72
|
+
attributeBasedShippingOptions &&
|
|
73
|
+
Object.keys(attributeBasedShippingOptions).length > 0
|
|
74
|
+
) {
|
|
75
|
+
return filteredPaymentOptions.filter(
|
|
76
|
+
(option) => option.slug?.toLowerCase() !== 'pay-on-delivery'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return filteredPaymentOptions;
|
|
80
|
+
}, [filteredPaymentOptions, attributeBasedShippingOptions]);
|
|
81
|
+
|
|
82
|
+
const handleKeyDown = useCallback(
|
|
83
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
84
|
+
if (
|
|
85
|
+
!['ArrowRight', 'ArrowLeft', 'ArrowDown', 'ArrowUp', 'Home', 'End'].includes(
|
|
86
|
+
event.key
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
const buttons = Array.from(
|
|
93
|
+
containerRef.current?.querySelectorAll<HTMLButtonElement>(
|
|
94
|
+
'button[role="radio"]'
|
|
95
|
+
) ?? []
|
|
96
|
+
);
|
|
97
|
+
if (buttons.length === 0) return;
|
|
98
|
+
const currentIndex = buttons.findIndex(
|
|
99
|
+
(btn) => btn === document.activeElement
|
|
100
|
+
);
|
|
101
|
+
let nextIndex = currentIndex;
|
|
102
|
+
switch (event.key) {
|
|
103
|
+
case 'ArrowRight':
|
|
104
|
+
case 'ArrowDown':
|
|
105
|
+
nextIndex = (currentIndex + 1 + buttons.length) % buttons.length;
|
|
106
|
+
break;
|
|
107
|
+
case 'ArrowLeft':
|
|
108
|
+
case 'ArrowUp':
|
|
109
|
+
nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
|
|
110
|
+
break;
|
|
111
|
+
case 'Home':
|
|
112
|
+
nextIndex = 0;
|
|
113
|
+
break;
|
|
114
|
+
case 'End':
|
|
115
|
+
nextIndex = buttons.length - 1;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
buttons[nextIndex]?.focus();
|
|
119
|
+
},
|
|
120
|
+
[]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (visibleOptions.length === 0) {
|
|
124
|
+
return (
|
|
125
|
+
<p className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-5 py-4 text-sm italic text-gray-600">
|
|
126
|
+
{t('checkout.one_page.payment.no_options')}
|
|
127
|
+
</p>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
ref={containerRef}
|
|
134
|
+
className={clsx(
|
|
135
|
+
'grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-3',
|
|
136
|
+
{ 'pointer-events-none opacity-60': isLoading }
|
|
137
|
+
)}
|
|
138
|
+
role="radiogroup"
|
|
139
|
+
aria-label={t('checkout.one_page.payment.title')}
|
|
140
|
+
onKeyDown={handleKeyDown}
|
|
141
|
+
>
|
|
142
|
+
{visibleOptions.map((option, index) => {
|
|
143
|
+
const selected = preOrder?.payment_option?.pk === option.pk;
|
|
144
|
+
return (
|
|
145
|
+
<button
|
|
146
|
+
key={option.pk}
|
|
147
|
+
type="button"
|
|
148
|
+
role="radio"
|
|
149
|
+
aria-checked={selected}
|
|
150
|
+
tabIndex={selected || (!preOrder?.payment_option && index === 0) ? 0 : -1}
|
|
151
|
+
onClick={() => setPaymentOption(option.pk)}
|
|
152
|
+
className={clsx(
|
|
153
|
+
'group relative flex min-h-[5rem] items-center gap-3',
|
|
154
|
+
'rounded-xl border px-4 py-3 text-left',
|
|
155
|
+
'transition-all duration-200 ease-out',
|
|
156
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-black-800 focus-visible:ring-offset-2',
|
|
157
|
+
selected
|
|
158
|
+
? 'border-black-800 bg-black-800/[0.03] shadow-[inset_0_0_0_1px_rgb(31,31,31)]'
|
|
159
|
+
: 'border-gray-200 bg-white hover:border-gray-400'
|
|
160
|
+
)}
|
|
161
|
+
data-testid={`one-page-payment-card-${option.pk}`}
|
|
162
|
+
data-payment-type={option.payment_type}
|
|
163
|
+
>
|
|
164
|
+
<Icon
|
|
165
|
+
name={getOptionIcon(option)}
|
|
166
|
+
size={20}
|
|
167
|
+
className={clsx(
|
|
168
|
+
'flex-shrink-0 transition-colors',
|
|
169
|
+
selected ? 'fill-black-800' : 'fill-gray-600'
|
|
170
|
+
)}
|
|
171
|
+
aria-hidden="true"
|
|
172
|
+
/>
|
|
173
|
+
<span
|
|
174
|
+
className={clsx(
|
|
175
|
+
'flex-1 text-sm font-medium leading-tight',
|
|
176
|
+
selected ? 'text-black-800' : 'text-gray-700'
|
|
177
|
+
)}
|
|
178
|
+
>
|
|
179
|
+
{option.name}
|
|
180
|
+
</span>
|
|
181
|
+
<span
|
|
182
|
+
className={clsx(
|
|
183
|
+
'flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full border-2 transition-all',
|
|
184
|
+
selected
|
|
185
|
+
? 'border-black-800 bg-black-800'
|
|
186
|
+
: 'border-gray-300 bg-white group-hover:border-gray-400'
|
|
187
|
+
)}
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
>
|
|
190
|
+
{selected && (
|
|
191
|
+
<span className="block h-1.5 w-1.5 rounded-full bg-white" />
|
|
192
|
+
)}
|
|
193
|
+
</span>
|
|
194
|
+
</button>
|
|
195
|
+
);
|
|
196
|
+
})}
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export default PaymentOptionsGrid;
|