@pradip1995/segment-jewelry-checkout-form 0.1.3 → 0.1.4
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/package.json +6 -6
- package/src/address-fields.tsx +211 -99
- package/src/jewelry-checkout-form.css +276 -45
- package/src/leave-checkout-guard.tsx +198 -0
- package/src/segment.tsx +15 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-jewelry-checkout-form",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,10 +16,6 @@
|
|
|
16
16
|
"./manifest": "./src/manifest.ts",
|
|
17
17
|
"./payment-block": "./src/payment-block.tsx"
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"typecheck": "tsc --noEmit",
|
|
21
|
-
"lint": "tsc --noEmit"
|
|
22
|
-
},
|
|
23
19
|
"peerDependencies": {
|
|
24
20
|
"@pradip1995/commerce-core": "^4.0.0",
|
|
25
21
|
"@pradip1995/plugin-sdk": "^0.2.0",
|
|
@@ -38,5 +34,9 @@
|
|
|
38
34
|
"@types/react": "^19",
|
|
39
35
|
"react": "19.0.3",
|
|
40
36
|
"typescript": "^5.7.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"lint": "tsc --noEmit"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
package/src/address-fields.tsx
CHANGED
|
@@ -100,22 +100,37 @@ function addressToFormValues(
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function
|
|
104
|
-
|
|
103
|
+
function pickPreferredFromList(
|
|
104
|
+
addresses: HttpTypes.StoreCustomerAddress[]
|
|
105
105
|
): HttpTypes.StoreCustomerAddress | undefined {
|
|
106
|
-
const addresses = customer?.addresses ?? []
|
|
107
106
|
return (
|
|
108
107
|
addresses.find((a) => a.is_default_shipping) ||
|
|
108
|
+
addresses.find((a) => a.is_default_billing) ||
|
|
109
109
|
addresses.find(
|
|
110
|
-
(a) =>
|
|
111
|
-
a.is_default_billing ||
|
|
112
|
-
a.metadata?.is_default === true ||
|
|
113
|
-
a.metadata?.is_default === "true"
|
|
110
|
+
(a) => a.metadata?.is_default === true || a.metadata?.is_default === "true"
|
|
114
111
|
) ||
|
|
115
112
|
addresses[0]
|
|
116
113
|
)
|
|
117
114
|
}
|
|
118
115
|
|
|
116
|
+
function pickPreferredAddress(
|
|
117
|
+
customer?: HttpTypes.StoreCustomer | null
|
|
118
|
+
): HttpTypes.StoreCustomerAddress | undefined {
|
|
119
|
+
return pickPreferredFromList(customer?.addresses ?? [])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sortAddressesDefaultFirst(
|
|
123
|
+
addresses: HttpTypes.StoreCustomerAddress[]
|
|
124
|
+
): HttpTypes.StoreCustomerAddress[] {
|
|
125
|
+
const preferred = pickPreferredFromList(addresses)
|
|
126
|
+
if (!preferred?.id) return addresses
|
|
127
|
+
return [...addresses].sort((left, right) => {
|
|
128
|
+
if (left.id === preferred.id) return -1
|
|
129
|
+
if (right.id === preferred.id) return 1
|
|
130
|
+
return 0
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
119
134
|
function findMatchingSavedAddress(
|
|
120
135
|
cart: HttpTypes.StoreCart | undefined,
|
|
121
136
|
addresses: HttpTypes.StoreCustomerAddress[]
|
|
@@ -143,7 +158,7 @@ function buildInitialValues(
|
|
|
143
158
|
const shipping = cart?.shipping_address
|
|
144
159
|
|
|
145
160
|
if (addresses.length) {
|
|
146
|
-
const source =
|
|
161
|
+
const source = preferred || matched
|
|
147
162
|
if (source) {
|
|
148
163
|
return addressToFormValues(source, {
|
|
149
164
|
email,
|
|
@@ -179,29 +194,137 @@ function resolveInitialSelectedId(
|
|
|
179
194
|
const addresses = customer?.addresses ?? []
|
|
180
195
|
if (!addresses.length) return NEW_ADDRESS_ID
|
|
181
196
|
|
|
182
|
-
const matched = findMatchingSavedAddress(cart, addresses)
|
|
183
|
-
if (matched?.id) return matched.id
|
|
184
|
-
|
|
185
197
|
const preferred = pickPreferredAddress(customer)
|
|
186
198
|
if (preferred?.id) return preferred.id
|
|
187
199
|
|
|
200
|
+
const matched = findMatchingSavedAddress(cart, addresses)
|
|
201
|
+
if (matched?.id) return matched.id
|
|
202
|
+
|
|
188
203
|
return NEW_ADDRESS_ID
|
|
189
204
|
}
|
|
190
205
|
|
|
191
|
-
function
|
|
206
|
+
function formatAddressParts(address: {
|
|
192
207
|
address1?: string | null
|
|
193
208
|
address2?: string | null
|
|
194
209
|
city?: string | null
|
|
195
210
|
province?: string | null
|
|
196
211
|
postalCode?: string | null
|
|
197
212
|
countryCode?: string | null
|
|
198
|
-
}): string {
|
|
213
|
+
}): { street: string; cityLine: string } {
|
|
214
|
+
const street = [address.address1, address.address2].filter(Boolean).join(", ")
|
|
199
215
|
const locality = [address.city, address.province].filter(Boolean).join(", ")
|
|
200
216
|
const pin = address.postalCode ? `${locality ? " - " : ""}${address.postalCode}` : ""
|
|
201
217
|
const country = (address.countryCode || "").toUpperCase()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
const cityLine = [`${locality}${pin}`, country].filter(Boolean).join(", ")
|
|
219
|
+
return { street, cityLine }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function formatMobileDisplay(phone?: string | null): string {
|
|
223
|
+
const digits = (phone || "").replace(/\D/g, "")
|
|
224
|
+
if (!digits) return ""
|
|
225
|
+
if (digits.length === 10) return `91${digits}`
|
|
226
|
+
return digits
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function resolveAddressType(value: unknown): "HOME" | "OFFICE" {
|
|
230
|
+
return String(value ?? "").trim().toUpperCase() === "OFFICE" ? "OFFICE" : "HOME"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function addressTypeFromMetadata(metadata: unknown): "HOME" | "OFFICE" {
|
|
234
|
+
if (!metadata || typeof metadata !== "object") return "HOME"
|
|
235
|
+
return resolveAddressType((metadata as Record<string, unknown>).address_type)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function CheckoutAddressCard({
|
|
239
|
+
name,
|
|
240
|
+
street,
|
|
241
|
+
cityLine,
|
|
242
|
+
phone,
|
|
243
|
+
addressType = "HOME",
|
|
244
|
+
selected = false,
|
|
245
|
+
onEdit,
|
|
246
|
+
onSelect,
|
|
247
|
+
}: {
|
|
248
|
+
name: string
|
|
249
|
+
street: string
|
|
250
|
+
cityLine: string
|
|
251
|
+
phone?: string | null
|
|
252
|
+
addressType?: "HOME" | "OFFICE"
|
|
253
|
+
selected?: boolean
|
|
254
|
+
onEdit?: () => void
|
|
255
|
+
onSelect?: () => void
|
|
256
|
+
}) {
|
|
257
|
+
const mobile = formatMobileDisplay(phone)
|
|
258
|
+
const interactive = Boolean(onSelect)
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
className={[
|
|
263
|
+
"jewelry-checkout-address-card__body",
|
|
264
|
+
selected ? "jewelry-checkout-address-card__body--selected" : "",
|
|
265
|
+
interactive ? "jewelry-checkout-address-card__body--pickable" : "",
|
|
266
|
+
].join(" ")}
|
|
267
|
+
role={interactive ? "button" : undefined}
|
|
268
|
+
tabIndex={interactive ? 0 : undefined}
|
|
269
|
+
aria-pressed={interactive ? selected : undefined}
|
|
270
|
+
onClick={onSelect}
|
|
271
|
+
onKeyDown={
|
|
272
|
+
onSelect
|
|
273
|
+
? (event) => {
|
|
274
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
275
|
+
event.preventDefault()
|
|
276
|
+
onSelect()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
: undefined
|
|
280
|
+
}
|
|
281
|
+
>
|
|
282
|
+
<span
|
|
283
|
+
className={[
|
|
284
|
+
"jewelry-checkout-address-card__radio",
|
|
285
|
+
selected ? "" : "jewelry-checkout-address-card__radio--empty",
|
|
286
|
+
].join(" ")}
|
|
287
|
+
aria-hidden
|
|
288
|
+
>
|
|
289
|
+
<span />
|
|
290
|
+
</span>
|
|
291
|
+
<div className="jewelry-checkout-address-card__content">
|
|
292
|
+
<div className="jewelry-checkout-address-card__name-row">
|
|
293
|
+
<span className="jewelry-checkout-address-card__name">{name}</span>
|
|
294
|
+
<span className="jewelry-checkout-address-card__tag">{addressType === "OFFICE" ? "Office" : "Home"}</span>
|
|
295
|
+
</div>
|
|
296
|
+
{street ? <span className="jewelry-checkout-address-card__line">{street}</span> : null}
|
|
297
|
+
{cityLine ? (
|
|
298
|
+
<span className="jewelry-checkout-address-card__line jewelry-checkout-address-card__line--city">
|
|
299
|
+
{cityLine}
|
|
300
|
+
</span>
|
|
301
|
+
) : null}
|
|
302
|
+
{mobile ? (
|
|
303
|
+
<span className="jewelry-checkout-address-card__mobile">
|
|
304
|
+
<span>Mobile</span> {mobile}
|
|
305
|
+
</span>
|
|
306
|
+
) : null}
|
|
307
|
+
<span className="jewelry-checkout-address-card__pod">Pay on Delivery available</span>
|
|
308
|
+
</div>
|
|
309
|
+
{onEdit ? (
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
className="jewelry-checkout-address-card__edit"
|
|
313
|
+
onClick={onEdit}
|
|
314
|
+
aria-label="Edit delivery address"
|
|
315
|
+
>
|
|
316
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
|
317
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 20h9" />
|
|
318
|
+
<path
|
|
319
|
+
strokeLinecap="round"
|
|
320
|
+
strokeLinejoin="round"
|
|
321
|
+
d="M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4 12.5-12.5z"
|
|
322
|
+
/>
|
|
323
|
+
</svg>
|
|
324
|
+
</button>
|
|
325
|
+
) : null}
|
|
326
|
+
</div>
|
|
327
|
+
)
|
|
205
328
|
}
|
|
206
329
|
|
|
207
330
|
function composeFullName(firstName: string, lastName: string): string {
|
|
@@ -249,7 +372,7 @@ export default function AddressFields({
|
|
|
249
372
|
onValuesChange,
|
|
250
373
|
}: AddressFieldsProps) {
|
|
251
374
|
const router = useRouter()
|
|
252
|
-
const savedAddresses = customer?.addresses ?? []
|
|
375
|
+
const savedAddresses = sortAddressesDefaultFirst(customer?.addresses ?? [])
|
|
253
376
|
const [values, setValues] = useState(() => buildInitialValues(cart, customer))
|
|
254
377
|
const [selectedAddressId, setSelectedAddressId] = useState(() =>
|
|
255
378
|
resolveInitialSelectedId(cart, customer)
|
|
@@ -259,6 +382,7 @@ export default function AddressFields({
|
|
|
259
382
|
const [draft, setDraft] = useState<CheckoutFormValues | null>(null)
|
|
260
383
|
const [draftError, setDraftError] = useState("")
|
|
261
384
|
const [saveAsDefault, setSaveAsDefault] = useState(true)
|
|
385
|
+
const [addressType, setAddressType] = useState<"HOME" | "OFFICE">("HOME")
|
|
262
386
|
const [acceptedManualAddress, setAcceptedManualAddress] = useState(false)
|
|
263
387
|
const [phoneError, setPhoneError] = useState(false)
|
|
264
388
|
const [sameAsBilling, setSameAsBilling] = useState(true)
|
|
@@ -291,12 +415,18 @@ export default function AddressFields({
|
|
|
291
415
|
|
|
292
416
|
useEffect(() => {
|
|
293
417
|
if (didPrefillDefaultRef.current) return
|
|
294
|
-
if (cart.shipping_address?.address_1) return
|
|
295
418
|
if (selectedAddressId === NEW_ADDRESS_ID) return
|
|
296
419
|
|
|
297
420
|
const selected = savedAddresses.find((address) => address.id === selectedAddressId)
|
|
298
421
|
if (!selected) return
|
|
299
422
|
|
|
423
|
+
const cartAlreadyMatches =
|
|
424
|
+
findMatchingSavedAddress(cart, [selected])?.id === selected.id
|
|
425
|
+
if (cartAlreadyMatches) {
|
|
426
|
+
didPrefillDefaultRef.current = true
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
300
430
|
didPrefillDefaultRef.current = true
|
|
301
431
|
void applySavedAddress(selected, { refresh: true })
|
|
302
432
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- run once on mount for default address prefill
|
|
@@ -436,6 +566,7 @@ export default function AddressFields({
|
|
|
436
566
|
const openAddAddress = () => {
|
|
437
567
|
setDraftError("")
|
|
438
568
|
setSaveAsDefault(savedAddresses.length === 0)
|
|
569
|
+
setAddressType("HOME")
|
|
439
570
|
setDraft(emptyAddressDraft(values))
|
|
440
571
|
setAddressModal("add")
|
|
441
572
|
}
|
|
@@ -447,6 +578,7 @@ export default function AddressFields({
|
|
|
447
578
|
!!(editingSaved?.is_default_shipping || editingSaved?.is_default_billing) ||
|
|
448
579
|
savedAddresses.length === 0
|
|
449
580
|
)
|
|
581
|
+
setAddressType(addressTypeFromMetadata(editingSaved?.metadata))
|
|
450
582
|
setDraft({ ...values })
|
|
451
583
|
setAddressModal("edit")
|
|
452
584
|
}
|
|
@@ -523,6 +655,7 @@ export default function AddressFields({
|
|
|
523
655
|
formData.set("postal_code", nextValues.postalCode)
|
|
524
656
|
formData.set("country_code", nextValues.countryCode)
|
|
525
657
|
formData.set("phone", nextValues.phone)
|
|
658
|
+
formData.set("address_type", addressType)
|
|
526
659
|
if (saveAsDefault) formData.set("is_default", "on")
|
|
527
660
|
|
|
528
661
|
try {
|
|
@@ -566,7 +699,9 @@ export default function AddressFields({
|
|
|
566
699
|
const hasSavedAddresses = savedAddresses.length > 0
|
|
567
700
|
const hasSelectedAddress = Boolean(selectedSaved) || acceptedManualAddress
|
|
568
701
|
const isDefaultSelected = !!(
|
|
569
|
-
selectedSaved?.is_default_shipping ||
|
|
702
|
+
selectedSaved?.is_default_shipping ||
|
|
703
|
+
(!savedAddresses.some((address) => address.is_default_shipping) &&
|
|
704
|
+
selectedSaved?.is_default_billing)
|
|
570
705
|
)
|
|
571
706
|
|
|
572
707
|
if (!hasSavedAddresses) {
|
|
@@ -775,6 +910,7 @@ export default function AddressFields({
|
|
|
775
910
|
onChange={(value) => updateDraft("email", value)}
|
|
776
911
|
full
|
|
777
912
|
/>
|
|
913
|
+
<AddressTypePicker value={addressType} onChange={setAddressType} />
|
|
778
914
|
<label className="jewelry-checkout-modal__check">
|
|
779
915
|
<input
|
|
780
916
|
type="checkbox"
|
|
@@ -839,45 +975,14 @@ export default function AddressFields({
|
|
|
839
975
|
<div className="jewelry-checkout-address-card__badge">
|
|
840
976
|
{isDefaultSelected ? "Default address" : "Delivery address"}
|
|
841
977
|
</div>
|
|
842
|
-
<
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
</p>
|
|
851
|
-
<span className="jewelry-checkout-address-card__tag">Home</span>
|
|
852
|
-
</div>
|
|
853
|
-
<p className="jewelry-checkout-address-card__line">
|
|
854
|
-
{formatAddressLine({
|
|
855
|
-
address1: values.address1,
|
|
856
|
-
address2: values.address2,
|
|
857
|
-
city: values.city,
|
|
858
|
-
province: values.province,
|
|
859
|
-
postalCode: values.postalCode,
|
|
860
|
-
countryCode: values.countryCode,
|
|
861
|
-
})}
|
|
862
|
-
</p>
|
|
863
|
-
<span className="jewelry-checkout-address-card__pod">Pay on Delivery available</span>
|
|
864
|
-
</div>
|
|
865
|
-
<button
|
|
866
|
-
type="button"
|
|
867
|
-
className="jewelry-checkout-address-card__edit"
|
|
868
|
-
onClick={openEditAddress}
|
|
869
|
-
aria-label="Edit delivery address"
|
|
870
|
-
>
|
|
871
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
|
872
|
-
<path strokeLinecap="round" strokeLinejoin="round" d="M12 20h9" />
|
|
873
|
-
<path
|
|
874
|
-
strokeLinecap="round"
|
|
875
|
-
strokeLinejoin="round"
|
|
876
|
-
d="M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4 12.5-12.5z"
|
|
877
|
-
/>
|
|
878
|
-
</svg>
|
|
879
|
-
</button>
|
|
880
|
-
</div>
|
|
978
|
+
<CheckoutAddressCard
|
|
979
|
+
name={values.fullName || "Delivery address"}
|
|
980
|
+
{...formatAddressParts(values)}
|
|
981
|
+
phone={values.phone}
|
|
982
|
+
addressType={addressTypeFromMetadata(selectedSaved?.metadata)}
|
|
983
|
+
selected
|
|
984
|
+
onEdit={openEditAddress}
|
|
985
|
+
/>
|
|
881
986
|
</article>
|
|
882
987
|
) : (
|
|
883
988
|
<div className="jewelry-checkout-address-empty">
|
|
@@ -922,51 +1027,29 @@ export default function AddressFields({
|
|
|
922
1027
|
<ul className="jewelry-checkout-modal__list">
|
|
923
1028
|
{savedAddresses.map((address) => {
|
|
924
1029
|
const isSelected = selectedAddressId === address.id
|
|
925
|
-
const isDefault = !!(address.is_default_shipping || address.is_default_billing)
|
|
926
1030
|
const label = formatAddressName(address) || "Saved address"
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1031
|
+
const parts = formatAddressParts({
|
|
1032
|
+
address1: address.address_1,
|
|
1033
|
+
address2: address.address_2,
|
|
1034
|
+
city: address.city,
|
|
1035
|
+
province: address.province,
|
|
1036
|
+
postalCode: address.postal_code,
|
|
1037
|
+
countryCode: address.country_code,
|
|
1038
|
+
})
|
|
935
1039
|
|
|
936
1040
|
return (
|
|
937
1041
|
<li key={address.id}>
|
|
938
|
-
<
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
className={[
|
|
950
|
-
"jewelry-checkout-address-card__radio",
|
|
951
|
-
isSelected ? "" : "jewelry-checkout-address-card__radio--empty",
|
|
952
|
-
].join(" ")}
|
|
953
|
-
aria-hidden
|
|
954
|
-
>
|
|
955
|
-
<span />
|
|
956
|
-
</span>
|
|
957
|
-
<span className="jewelry-checkout-modal__option-body">
|
|
958
|
-
<span className="jewelry-checkout-modal__option-name">
|
|
959
|
-
{label}
|
|
960
|
-
<span className="jewelry-checkout-address-card__tag">Home</span>
|
|
961
|
-
{isDefault ? (
|
|
962
|
-
<span className="jewelry-checkout-address-card__tag jewelry-checkout-address-card__tag--default">
|
|
963
|
-
Default
|
|
964
|
-
</span>
|
|
965
|
-
) : null}
|
|
966
|
-
</span>
|
|
967
|
-
<span className="jewelry-checkout-modal__option-line">{line}</span>
|
|
968
|
-
</span>
|
|
969
|
-
</button>
|
|
1042
|
+
<CheckoutAddressCard
|
|
1043
|
+
name={label}
|
|
1044
|
+
street={parts.street}
|
|
1045
|
+
cityLine={parts.cityLine}
|
|
1046
|
+
phone={address.phone}
|
|
1047
|
+
addressType={addressTypeFromMetadata(address.metadata)}
|
|
1048
|
+
selected={isSelected}
|
|
1049
|
+
onSelect={
|
|
1050
|
+
isSelectingAddress ? undefined : () => handlePickSavedAddress(address)
|
|
1051
|
+
}
|
|
1052
|
+
/>
|
|
970
1053
|
</li>
|
|
971
1054
|
)
|
|
972
1055
|
})}
|
|
@@ -1050,6 +1133,7 @@ export default function AddressFields({
|
|
|
1050
1133
|
full
|
|
1051
1134
|
/>
|
|
1052
1135
|
|
|
1136
|
+
<AddressTypePicker value={addressType} onChange={setAddressType} />
|
|
1053
1137
|
<label className="jewelry-checkout-modal__check">
|
|
1054
1138
|
<input
|
|
1055
1139
|
type="checkbox"
|
|
@@ -1124,6 +1208,34 @@ function CheckoutAddressModal({
|
|
|
1124
1208
|
)
|
|
1125
1209
|
}
|
|
1126
1210
|
|
|
1211
|
+
function AddressTypePicker({
|
|
1212
|
+
value,
|
|
1213
|
+
onChange,
|
|
1214
|
+
}: {
|
|
1215
|
+
value: "HOME" | "OFFICE"
|
|
1216
|
+
onChange: (value: "HOME" | "OFFICE") => void
|
|
1217
|
+
}) {
|
|
1218
|
+
return (
|
|
1219
|
+
<fieldset className="jewelry-checkout-modal__type">
|
|
1220
|
+
<legend className="jewelry-checkout-modal__label">Address type</legend>
|
|
1221
|
+
<div className="jewelry-checkout-modal__type-options">
|
|
1222
|
+
{(["HOME", "OFFICE"] as const).map((type) => (
|
|
1223
|
+
<label key={type} className="jewelry-checkout-modal__type-option">
|
|
1224
|
+
<input
|
|
1225
|
+
type="radio"
|
|
1226
|
+
name="checkout_address_type"
|
|
1227
|
+
value={type}
|
|
1228
|
+
checked={value === type}
|
|
1229
|
+
onChange={() => onChange(type)}
|
|
1230
|
+
/>
|
|
1231
|
+
<span>{type === "HOME" ? "Home" : "Office"}</span>
|
|
1232
|
+
</label>
|
|
1233
|
+
))}
|
|
1234
|
+
</div>
|
|
1235
|
+
</fieldset>
|
|
1236
|
+
)
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1127
1239
|
function ModalField({
|
|
1128
1240
|
label,
|
|
1129
1241
|
id,
|
|
@@ -168,12 +168,14 @@
|
|
|
168
168
|
|
|
169
169
|
.jewelry-checkout-address__btn--outline {
|
|
170
170
|
background: #ffffff;
|
|
171
|
-
border: 1px solid #
|
|
172
|
-
color: #
|
|
171
|
+
border: 1px solid #1a1224;
|
|
172
|
+
color: #1a1224;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
.jewelry-checkout-address__btn--outline:hover:not(:disabled) {
|
|
176
176
|
background: #f7f3fa;
|
|
177
|
+
border-color: #3b1f4e;
|
|
178
|
+
color: #3b1f4e;
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
.jewelry-checkout-address__btn--block {
|
|
@@ -208,10 +210,26 @@
|
|
|
208
210
|
display: flex;
|
|
209
211
|
align-items: flex-start;
|
|
210
212
|
gap: 0.85rem;
|
|
211
|
-
padding: 1.
|
|
212
|
-
border: 1px solid #
|
|
213
|
-
border-radius: 0.
|
|
213
|
+
padding: 1.15rem 2.85rem 1.15rem 1.1rem;
|
|
214
|
+
border: 1px solid #e4dcd3;
|
|
215
|
+
border-radius: 0.55rem;
|
|
214
216
|
background: #ffffff;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.jewelry-checkout-address-card__body--selected {
|
|
221
|
+
background: #faf6f1;
|
|
222
|
+
border-color: #e4dcd3;
|
|
223
|
+
box-shadow: inset 4px 0 0 #3b1f4e;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.jewelry-checkout-address-card__body--pickable {
|
|
227
|
+
width: 100%;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.jewelry-checkout-address-card__body--pickable:hover {
|
|
232
|
+
border-color: #3b1f4e;
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
.jewelry-checkout-address-card__radio {
|
|
@@ -220,23 +238,24 @@
|
|
|
220
238
|
justify-content: center;
|
|
221
239
|
width: 1.125rem;
|
|
222
240
|
height: 1.125rem;
|
|
223
|
-
margin-top: 0.
|
|
241
|
+
margin-top: 0.2rem;
|
|
224
242
|
flex-shrink: 0;
|
|
225
243
|
border: 1.5px solid #3b1f4e;
|
|
226
244
|
border-radius: 999px;
|
|
227
|
-
background: #
|
|
245
|
+
background: #3b1f4e;
|
|
228
246
|
box-sizing: border-box;
|
|
229
247
|
}
|
|
230
248
|
|
|
231
249
|
.jewelry-checkout-address-card__radio span {
|
|
232
|
-
width: 0.
|
|
233
|
-
height: 0.
|
|
250
|
+
width: 0.35rem;
|
|
251
|
+
height: 0.35rem;
|
|
234
252
|
border-radius: 999px;
|
|
235
|
-
background: #
|
|
253
|
+
background: #faf6f1;
|
|
236
254
|
}
|
|
237
255
|
|
|
238
256
|
.jewelry-checkout-address-card__radio--empty {
|
|
239
|
-
|
|
257
|
+
background: #ffffff;
|
|
258
|
+
border-color: #c4b9c9;
|
|
240
259
|
}
|
|
241
260
|
|
|
242
261
|
.jewelry-checkout-address-card__radio--empty span {
|
|
@@ -246,6 +265,9 @@
|
|
|
246
265
|
.jewelry-checkout-address-card__content {
|
|
247
266
|
min-width: 0;
|
|
248
267
|
flex: 1;
|
|
268
|
+
display: flex;
|
|
269
|
+
flex-direction: column;
|
|
270
|
+
align-items: flex-start;
|
|
249
271
|
}
|
|
250
272
|
|
|
251
273
|
.jewelry-checkout-address-card__name-row {
|
|
@@ -257,18 +279,20 @@
|
|
|
257
279
|
|
|
258
280
|
.jewelry-checkout-address-card__name {
|
|
259
281
|
margin: 0;
|
|
282
|
+
display: inline;
|
|
260
283
|
font-family: var(--font-sans, inherit);
|
|
261
284
|
font-size: 0.9375rem;
|
|
262
285
|
font-weight: 700;
|
|
263
|
-
color: #
|
|
286
|
+
color: #3b1f4e;
|
|
264
287
|
}
|
|
265
288
|
|
|
266
289
|
.jewelry-checkout-address-card__tag {
|
|
267
290
|
display: inline-flex;
|
|
268
|
-
padding: 0.
|
|
269
|
-
border-radius: 0.
|
|
270
|
-
|
|
271
|
-
|
|
291
|
+
padding: 0.1rem 0.45rem;
|
|
292
|
+
border-radius: 0.22rem;
|
|
293
|
+
border: 1px solid #cfc9d4;
|
|
294
|
+
background: transparent;
|
|
295
|
+
color: #6b6573;
|
|
272
296
|
font-size: 0.5625rem;
|
|
273
297
|
font-weight: 700;
|
|
274
298
|
letter-spacing: 0.08em;
|
|
@@ -277,26 +301,49 @@
|
|
|
277
301
|
|
|
278
302
|
.jewelry-checkout-address-card__tag--default {
|
|
279
303
|
background: #3b1f4e;
|
|
304
|
+
border-color: #3b1f4e;
|
|
280
305
|
color: #ffffff;
|
|
281
306
|
}
|
|
282
307
|
|
|
283
308
|
.jewelry-checkout-address-card__line {
|
|
284
|
-
|
|
309
|
+
display: block;
|
|
310
|
+
margin: 0.35rem 0 0;
|
|
285
311
|
font-size: 0.8125rem;
|
|
286
|
-
line-height: 1.
|
|
287
|
-
color: #
|
|
312
|
+
line-height: 1.5;
|
|
313
|
+
color: #4a4453;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.jewelry-checkout-address-card__line--city {
|
|
317
|
+
margin-top: 0.08rem;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.jewelry-checkout-address-card__mobile {
|
|
321
|
+
display: block;
|
|
322
|
+
margin: 0.45rem 0 0;
|
|
323
|
+
font-size: 0.8125rem;
|
|
324
|
+
font-weight: 600;
|
|
325
|
+
color: #1a1224;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.jewelry-checkout-address-card__mobile span {
|
|
329
|
+
margin-right: 0.4rem;
|
|
330
|
+
font-size: 0.6875rem;
|
|
331
|
+
font-weight: 600;
|
|
332
|
+
letter-spacing: 0.08em;
|
|
333
|
+
text-transform: uppercase;
|
|
334
|
+
color: #8a8490;
|
|
288
335
|
}
|
|
289
336
|
|
|
290
337
|
.jewelry-checkout-address-card__pod {
|
|
291
338
|
display: inline-flex;
|
|
292
339
|
margin-top: 0.7rem;
|
|
293
|
-
padding: 0.
|
|
294
|
-
border-radius:
|
|
295
|
-
background: #
|
|
296
|
-
color: #
|
|
297
|
-
font-size: 0.
|
|
298
|
-
font-weight:
|
|
299
|
-
letter-spacing: 0.
|
|
340
|
+
padding: 0.22rem 0.65rem;
|
|
341
|
+
border-radius: 999px;
|
|
342
|
+
background: #e8f6ec;
|
|
343
|
+
color: #1b7a3a;
|
|
344
|
+
font-size: 0.6875rem;
|
|
345
|
+
font-weight: 600;
|
|
346
|
+
letter-spacing: 0.01em;
|
|
300
347
|
}
|
|
301
348
|
|
|
302
349
|
.jewelry-checkout-address-card__edit {
|
|
@@ -306,17 +353,20 @@
|
|
|
306
353
|
display: inline-flex;
|
|
307
354
|
align-items: center;
|
|
308
355
|
justify-content: center;
|
|
309
|
-
width:
|
|
310
|
-
height:
|
|
311
|
-
border: 1px solid #
|
|
312
|
-
border-radius: 0.
|
|
313
|
-
background: #
|
|
314
|
-
color: #
|
|
356
|
+
width: 2rem;
|
|
357
|
+
height: 2rem;
|
|
358
|
+
border: 1px solid #e4dcd3;
|
|
359
|
+
border-radius: 0.4rem;
|
|
360
|
+
background: #ffffff;
|
|
361
|
+
color: #5a5463;
|
|
315
362
|
cursor: pointer;
|
|
363
|
+
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
|
316
364
|
}
|
|
317
365
|
|
|
318
366
|
.jewelry-checkout-address-card__edit:hover {
|
|
319
|
-
background: #
|
|
367
|
+
background: #f7f3fa;
|
|
368
|
+
border-color: #3b1f4e;
|
|
369
|
+
color: #3b1f4e;
|
|
320
370
|
}
|
|
321
371
|
|
|
322
372
|
.jewelry-checkout-address-empty {
|
|
@@ -429,23 +479,17 @@
|
|
|
429
479
|
}
|
|
430
480
|
|
|
431
481
|
.jewelry-checkout-modal__option {
|
|
432
|
-
display:
|
|
433
|
-
align-items: flex-start;
|
|
434
|
-
gap: 0.85rem;
|
|
482
|
+
display: block;
|
|
435
483
|
width: 100%;
|
|
436
|
-
padding:
|
|
484
|
+
padding: 0;
|
|
437
485
|
text-align: left;
|
|
438
|
-
border:
|
|
439
|
-
|
|
440
|
-
background: #ffffff;
|
|
486
|
+
border: 0;
|
|
487
|
+
background: transparent;
|
|
441
488
|
cursor: pointer;
|
|
442
|
-
transition: border-color 0.2s ease, background 0.2s ease;
|
|
443
489
|
}
|
|
444
490
|
|
|
445
|
-
.jewelry-checkout-modal__option:hover:not(:disabled)
|
|
446
|
-
.jewelry-checkout-modal__option--selected {
|
|
491
|
+
.jewelry-checkout-modal__option:hover:not(:disabled) .jewelry-checkout-address-card__body {
|
|
447
492
|
border-color: #3b1f4e;
|
|
448
|
-
background: #faf8fc;
|
|
449
493
|
}
|
|
450
494
|
|
|
451
495
|
.jewelry-checkout-modal__option:disabled {
|
|
@@ -512,7 +556,8 @@
|
|
|
512
556
|
.jewelry-checkout-modal__field--full,
|
|
513
557
|
.jewelry-checkout-modal__check,
|
|
514
558
|
.jewelry-checkout-modal__error,
|
|
515
|
-
.jewelry-checkout-modal__actions
|
|
559
|
+
.jewelry-checkout-modal__actions,
|
|
560
|
+
.jewelry-checkout-modal__type {
|
|
516
561
|
grid-column: 1 / -1;
|
|
517
562
|
}
|
|
518
563
|
|
|
@@ -556,6 +601,55 @@
|
|
|
556
601
|
accent-color: #3b1f4e;
|
|
557
602
|
}
|
|
558
603
|
|
|
604
|
+
.jewelry-checkout-modal__type {
|
|
605
|
+
margin: 0;
|
|
606
|
+
padding: 0;
|
|
607
|
+
border: 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.jewelry-checkout-modal__type-options {
|
|
611
|
+
display: flex;
|
|
612
|
+
flex-wrap: wrap;
|
|
613
|
+
gap: 0.5rem;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.jewelry-checkout-modal__type-option {
|
|
617
|
+
position: relative;
|
|
618
|
+
cursor: pointer;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.jewelry-checkout-modal__type-option input {
|
|
622
|
+
position: absolute;
|
|
623
|
+
opacity: 0;
|
|
624
|
+
pointer-events: none;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.jewelry-checkout-modal__type-option span {
|
|
628
|
+
display: inline-flex;
|
|
629
|
+
align-items: center;
|
|
630
|
+
justify-content: center;
|
|
631
|
+
min-height: 2.4rem;
|
|
632
|
+
padding: 0 1.15rem;
|
|
633
|
+
border-radius: 999px;
|
|
634
|
+
border: 1px solid #e6e0ea;
|
|
635
|
+
background: #ffffff;
|
|
636
|
+
color: #1a1224;
|
|
637
|
+
font-size: 0.75rem;
|
|
638
|
+
font-weight: 700;
|
|
639
|
+
letter-spacing: 0.08em;
|
|
640
|
+
text-transform: uppercase;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.jewelry-checkout-modal__type-option input:focus-visible + span {
|
|
644
|
+
box-shadow: 0 0 0 3px rgba(59, 31, 78, 0.16);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.jewelry-checkout-modal__type-option input:checked + span {
|
|
648
|
+
background: #3b1f4e;
|
|
649
|
+
border-color: #3b1f4e;
|
|
650
|
+
color: #ffffff;
|
|
651
|
+
}
|
|
652
|
+
|
|
559
653
|
.jewelry-checkout-modal__grid {
|
|
560
654
|
display: contents;
|
|
561
655
|
}
|
|
@@ -630,3 +724,140 @@
|
|
|
630
724
|
flex: 1;
|
|
631
725
|
}
|
|
632
726
|
}
|
|
727
|
+
|
|
728
|
+
.jewelry-leave-checkout {
|
|
729
|
+
position: fixed;
|
|
730
|
+
inset: 0;
|
|
731
|
+
z-index: 100000;
|
|
732
|
+
display: flex;
|
|
733
|
+
align-items: center;
|
|
734
|
+
justify-content: center;
|
|
735
|
+
padding: 1.25rem;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.jewelry-leave-checkout__backdrop {
|
|
739
|
+
position: absolute;
|
|
740
|
+
inset: 0;
|
|
741
|
+
border: 0;
|
|
742
|
+
background: rgba(18, 12, 28, 0.46);
|
|
743
|
+
backdrop-filter: blur(8px);
|
|
744
|
+
-webkit-backdrop-filter: blur(8px);
|
|
745
|
+
cursor: pointer;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.jewelry-leave-checkout__dialog {
|
|
749
|
+
position: relative;
|
|
750
|
+
width: min(26.5rem, 100%);
|
|
751
|
+
overflow: hidden;
|
|
752
|
+
border-radius: 1.35rem;
|
|
753
|
+
background: #ffffff;
|
|
754
|
+
box-shadow: 0 28px 64px rgba(26, 18, 36, 0.24);
|
|
755
|
+
animation: jewelry-leave-checkout-in 0.22s ease;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
@keyframes jewelry-leave-checkout-in {
|
|
759
|
+
from {
|
|
760
|
+
opacity: 0;
|
|
761
|
+
transform: translateY(10px) scale(0.98);
|
|
762
|
+
}
|
|
763
|
+
to {
|
|
764
|
+
opacity: 1;
|
|
765
|
+
transform: translateY(0) scale(1);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.jewelry-leave-checkout__hero {
|
|
770
|
+
padding: 2.1rem 1.75rem 1.25rem;
|
|
771
|
+
text-align: center;
|
|
772
|
+
background: #f6f1ea;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.jewelry-leave-checkout__icon {
|
|
776
|
+
display: grid;
|
|
777
|
+
place-items: center;
|
|
778
|
+
width: 3.35rem;
|
|
779
|
+
height: 3.35rem;
|
|
780
|
+
margin: 0 auto 1rem;
|
|
781
|
+
border-radius: 999px;
|
|
782
|
+
background: #ffffff;
|
|
783
|
+
border: 1px solid rgba(59, 31, 78, 0.14);
|
|
784
|
+
color: #3b1f4e;
|
|
785
|
+
box-shadow: 0 6px 16px rgba(59, 31, 78, 0.08);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.jewelry-leave-checkout__title {
|
|
789
|
+
margin: 0;
|
|
790
|
+
font-family: var(--font-heading, Georgia, serif);
|
|
791
|
+
font-size: 1.4rem;
|
|
792
|
+
font-weight: 500;
|
|
793
|
+
letter-spacing: 0.1em;
|
|
794
|
+
text-transform: uppercase;
|
|
795
|
+
color: #3b1f4e;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.jewelry-leave-checkout__body {
|
|
799
|
+
padding: 1.35rem 1.75rem 1.75rem;
|
|
800
|
+
text-align: center;
|
|
801
|
+
background: #ffffff;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.jewelry-leave-checkout__copy {
|
|
805
|
+
margin: 0 0 1.5rem;
|
|
806
|
+
font-size: 0.9375rem;
|
|
807
|
+
line-height: 1.65;
|
|
808
|
+
color: #6b6573;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.jewelry-leave-checkout__actions {
|
|
812
|
+
display: grid;
|
|
813
|
+
grid-template-columns: 1fr 1fr;
|
|
814
|
+
gap: 0.75rem;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.jewelry-leave-checkout__btn {
|
|
818
|
+
display: inline-flex;
|
|
819
|
+
align-items: center;
|
|
820
|
+
justify-content: center;
|
|
821
|
+
min-height: 2.85rem;
|
|
822
|
+
padding: 0.7rem 0.85rem;
|
|
823
|
+
border-radius: 0.7rem;
|
|
824
|
+
font-family: var(--font-sans, inherit);
|
|
825
|
+
font-size: 0.6875rem;
|
|
826
|
+
font-weight: 700;
|
|
827
|
+
letter-spacing: 0.08em;
|
|
828
|
+
text-transform: uppercase;
|
|
829
|
+
cursor: pointer;
|
|
830
|
+
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.jewelry-leave-checkout__btn--stay {
|
|
834
|
+
background: #ffffff;
|
|
835
|
+
border: 1px solid #3b1f4e;
|
|
836
|
+
color: #3b1f4e;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.jewelry-leave-checkout__btn--stay:hover {
|
|
840
|
+
background: #f7f3fa;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.jewelry-leave-checkout__btn--leave {
|
|
844
|
+
background: #3b1f4e;
|
|
845
|
+
border: 1px solid #3b1f4e;
|
|
846
|
+
color: #ffffff;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.jewelry-leave-checkout__btn--leave:hover {
|
|
850
|
+
background: #55306c;
|
|
851
|
+
border-color: #55306c;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.jewelry-leave-checkout__btn:focus-visible {
|
|
855
|
+
outline: 2px solid #3b1f4e;
|
|
856
|
+
outline-offset: 2px;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
@media (max-width: 480px) {
|
|
860
|
+
.jewelry-leave-checkout__actions {
|
|
861
|
+
grid-template-columns: 1fr;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
4
|
+
import { createPortal } from "react-dom"
|
|
5
|
+
import { useParams, useRouter } from "next/navigation"
|
|
6
|
+
|
|
7
|
+
const DEFAULT_REGION = "in"
|
|
8
|
+
|
|
9
|
+
function isCheckoutPath(pathname: string): boolean {
|
|
10
|
+
return /\/checkout(\/|$)/.test(pathname)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolveHref(href: string): URL | null {
|
|
14
|
+
try {
|
|
15
|
+
return new URL(href, window.location.origin)
|
|
16
|
+
} catch {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function LeaveCheckoutGuard() {
|
|
22
|
+
const router = useRouter()
|
|
23
|
+
const params = useParams()
|
|
24
|
+
const countryCode = String(params?.countryCode || DEFAULT_REGION).toLowerCase()
|
|
25
|
+
const [open, setOpen] = useState(false)
|
|
26
|
+
const [mounted, setMounted] = useState(false)
|
|
27
|
+
const pendingHrefRef = useRef<string | null>(null)
|
|
28
|
+
const allowLeaveRef = useRef(false)
|
|
29
|
+
const openRef = useRef(false)
|
|
30
|
+
const stayButtonRef = useRef<HTMLButtonElement>(null)
|
|
31
|
+
openRef.current = open
|
|
32
|
+
|
|
33
|
+
const askToLeave = useCallback((href?: string | null) => {
|
|
34
|
+
pendingHrefRef.current = href ?? null
|
|
35
|
+
setOpen(true)
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
const stay = useCallback(() => {
|
|
39
|
+
pendingHrefRef.current = null
|
|
40
|
+
setOpen(false)
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
const leave = useCallback(() => {
|
|
44
|
+
allowLeaveRef.current = true
|
|
45
|
+
const href = pendingHrefRef.current
|
|
46
|
+
pendingHrefRef.current = null
|
|
47
|
+
setOpen(false)
|
|
48
|
+
router.push(href || `/${countryCode}/cart`)
|
|
49
|
+
}, [countryCode, router])
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setMounted(true)
|
|
53
|
+
}, [])
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!window.history.state?.jewelryCheckoutGuard) {
|
|
57
|
+
window.history.pushState({ jewelryCheckoutGuard: true }, "", window.location.href)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const onPopState = () => {
|
|
61
|
+
if (allowLeaveRef.current) return
|
|
62
|
+
window.history.pushState({ jewelryCheckoutGuard: true }, "", window.location.href)
|
|
63
|
+
askToLeave(null)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const onClick = (event: MouseEvent) => {
|
|
67
|
+
if (allowLeaveRef.current || openRef.current) return
|
|
68
|
+
if (event.defaultPrevented || event.button !== 0) return
|
|
69
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
|
|
70
|
+
|
|
71
|
+
const target = event.target as HTMLElement | null
|
|
72
|
+
if (!target) return
|
|
73
|
+
|
|
74
|
+
const anchor = target.closest("a[href]") as HTMLAnchorElement | null
|
|
75
|
+
if (!anchor) return
|
|
76
|
+
if (anchor.target === "_blank" || anchor.hasAttribute("download")) return
|
|
77
|
+
|
|
78
|
+
const href = anchor.getAttribute("href")
|
|
79
|
+
if (!href || href.startsWith("#") || href.startsWith("javascript:")) return
|
|
80
|
+
|
|
81
|
+
const url = resolveHref(href)
|
|
82
|
+
if (!url || url.origin !== window.location.origin) return
|
|
83
|
+
if (isCheckoutPath(url.pathname)) return
|
|
84
|
+
|
|
85
|
+
event.preventDefault()
|
|
86
|
+
event.stopPropagation()
|
|
87
|
+
askToLeave(`${url.pathname}${url.search}${url.hash}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const onSubmit = (event: SubmitEvent) => {
|
|
91
|
+
if (allowLeaveRef.current || openRef.current) return
|
|
92
|
+
const form = event.target as HTMLFormElement | null
|
|
93
|
+
if (!form) return
|
|
94
|
+
if (form.id === "checkout-address-form") return
|
|
95
|
+
if (form.getAttribute("role") !== "search" && !form.classList.contains("jewelry-nav__search")) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
event.stopPropagation()
|
|
101
|
+
event.stopImmediatePropagation()
|
|
102
|
+
|
|
103
|
+
const input = form.querySelector("input[type='search'], input[name='q']") as HTMLInputElement | null
|
|
104
|
+
const query = input?.value.trim() || ""
|
|
105
|
+
const base = `/${countryCode}/store`
|
|
106
|
+
askToLeave(query ? `${base}?q=${encodeURIComponent(query)}` : base)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
window.addEventListener("popstate", onPopState)
|
|
110
|
+
document.addEventListener("click", onClick, true)
|
|
111
|
+
document.addEventListener("submit", onSubmit, true)
|
|
112
|
+
|
|
113
|
+
return () => {
|
|
114
|
+
window.removeEventListener("popstate", onPopState)
|
|
115
|
+
document.removeEventListener("click", onClick, true)
|
|
116
|
+
document.removeEventListener("submit", onSubmit, true)
|
|
117
|
+
}
|
|
118
|
+
}, [askToLeave, countryCode])
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!open) return
|
|
122
|
+
const previous = document.body.style.overflow
|
|
123
|
+
document.body.style.overflow = "hidden"
|
|
124
|
+
stayButtonRef.current?.focus()
|
|
125
|
+
|
|
126
|
+
const onKey = (event: KeyboardEvent) => {
|
|
127
|
+
if (event.key === "Escape") stay()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
window.addEventListener("keydown", onKey)
|
|
131
|
+
return () => {
|
|
132
|
+
document.body.style.overflow = previous
|
|
133
|
+
window.removeEventListener("keydown", onKey)
|
|
134
|
+
}
|
|
135
|
+
}, [open, stay])
|
|
136
|
+
|
|
137
|
+
if (!mounted || !open) return null
|
|
138
|
+
|
|
139
|
+
return createPortal(
|
|
140
|
+
<div className="jewelry-leave-checkout" role="presentation">
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
className="jewelry-leave-checkout__backdrop"
|
|
144
|
+
aria-label="Stay in checkout"
|
|
145
|
+
onClick={stay}
|
|
146
|
+
/>
|
|
147
|
+
<div
|
|
148
|
+
className="jewelry-leave-checkout__dialog"
|
|
149
|
+
role="alertdialog"
|
|
150
|
+
aria-modal="true"
|
|
151
|
+
aria-labelledby="jewelry-leave-checkout-title"
|
|
152
|
+
aria-describedby="jewelry-leave-checkout-copy"
|
|
153
|
+
>
|
|
154
|
+
<div className="jewelry-leave-checkout__hero">
|
|
155
|
+
<span className="jewelry-leave-checkout__icon" aria-hidden>
|
|
156
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
|
|
157
|
+
<path
|
|
158
|
+
d="M12 3.6L21.4 20.2H2.6L12 3.6Z"
|
|
159
|
+
stroke="currentColor"
|
|
160
|
+
strokeWidth="1.7"
|
|
161
|
+
strokeLinejoin="round"
|
|
162
|
+
/>
|
|
163
|
+
<path d="M12 9.4v5.1" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
|
|
164
|
+
<circle cx="12" cy="16.9" r="0.85" fill="currentColor" />
|
|
165
|
+
</svg>
|
|
166
|
+
</span>
|
|
167
|
+
<h2 id="jewelry-leave-checkout-title" className="jewelry-leave-checkout__title">
|
|
168
|
+
Leave checkout?
|
|
169
|
+
</h2>
|
|
170
|
+
</div>
|
|
171
|
+
<div className="jewelry-leave-checkout__body">
|
|
172
|
+
<p id="jewelry-leave-checkout-copy" className="jewelry-leave-checkout__copy">
|
|
173
|
+
Your order details and progress will be lost if you leave now. Are you sure you want to
|
|
174
|
+
go back?
|
|
175
|
+
</p>
|
|
176
|
+
<div className="jewelry-leave-checkout__actions">
|
|
177
|
+
<button
|
|
178
|
+
ref={stayButtonRef}
|
|
179
|
+
type="button"
|
|
180
|
+
className="jewelry-leave-checkout__btn jewelry-leave-checkout__btn--stay"
|
|
181
|
+
onClick={stay}
|
|
182
|
+
>
|
|
183
|
+
Stay in checkout
|
|
184
|
+
</button>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
className="jewelry-leave-checkout__btn jewelry-leave-checkout__btn--leave"
|
|
188
|
+
onClick={leave}
|
|
189
|
+
>
|
|
190
|
+
Yes, leave
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>,
|
|
196
|
+
document.body
|
|
197
|
+
)
|
|
198
|
+
}
|
package/src/segment.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { HttpTypes } from "@medusajs/types"
|
|
4
4
|
import AddressFields from "./address-fields"
|
|
5
|
+
import LeaveCheckoutGuard from "./leave-checkout-guard"
|
|
5
6
|
import "./jewelry-checkout-form.css"
|
|
6
7
|
|
|
7
8
|
export default function JewelryCheckoutForm({
|
|
@@ -18,17 +19,23 @@ export default function JewelryCheckoutForm({
|
|
|
18
19
|
}) {
|
|
19
20
|
if (!cart?.items?.length) {
|
|
20
21
|
return (
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
22
|
+
<>
|
|
23
|
+
<LeaveCheckoutGuard />
|
|
24
|
+
<div className="card-surface rounded-lg p-6">
|
|
25
|
+
<p className="text-muted text-sm">Your cart is empty.</p>
|
|
26
|
+
</div>
|
|
27
|
+
</>
|
|
24
28
|
)
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
<>
|
|
33
|
+
<LeaveCheckoutGuard />
|
|
34
|
+
<div className="jewelry-checkout-form checkout-page__address-panel">
|
|
35
|
+
<form id="checkout-address-form" autoComplete="off">
|
|
36
|
+
<AddressFields cart={cart} customer={customer} formTitle={formTitle} />
|
|
37
|
+
</form>
|
|
38
|
+
</div>
|
|
39
|
+
</>
|
|
33
40
|
)
|
|
34
41
|
}
|