payaza-storefront-layouts 1.1.21 → 1.1.23
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/dist/components/checkout/ShippingOptionsPanel.d.ts +54 -0
- package/dist/components/checkout/ShippingOptionsPanel.d.ts.map +1 -0
- package/dist/components/checkout/ShippingOptionsPanel.js +115 -0
- package/dist/components/ui/guest-checkout-modal.d.ts.map +1 -1
- package/dist/components/ui/guest-checkout-modal.js +5 -2
- package/dist/hooks/use-payaza-checkout.d.ts.map +1 -1
- package/dist/hooks/use-payaza-checkout.js +5 -24
- package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/CheckoutPage.js +107 -34
- package/dist/lib/payaza-checkout.d.ts +6 -0
- package/dist/lib/payaza-checkout.d.ts.map +1 -1
- package/dist/lib/payaza-checkout.js +67 -21
- package/dist/lib/utils/phone-format.d.ts +5 -5
- package/dist/lib/utils/phone-format.d.ts.map +1 -1
- package/dist/lib/utils/phone-format.js +12 -26
- package/dist/styles/index.css +61 -22
- package/package.json +1 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ShippingQuoteAddress } from '../../lib/shipping-address.util';
|
|
2
|
+
export interface DeliveryRate {
|
|
3
|
+
providerKey: string;
|
|
4
|
+
providerDisplayName: string;
|
|
5
|
+
methodCode: string | null;
|
|
6
|
+
methodName: string;
|
|
7
|
+
etaTo: number | null;
|
|
8
|
+
etaFrom: number | null;
|
|
9
|
+
price: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
quoteToken: string | null;
|
|
12
|
+
serviceCode?: string;
|
|
13
|
+
courierId?: string;
|
|
14
|
+
notes: string | null;
|
|
15
|
+
isFastest?: boolean;
|
|
16
|
+
isCheapest?: boolean;
|
|
17
|
+
isRecommended?: boolean;
|
|
18
|
+
serviceType?: 'pickup' | 'dropoff';
|
|
19
|
+
supportsCOD?: boolean;
|
|
20
|
+
trackingLevel?: number;
|
|
21
|
+
}
|
|
22
|
+
/** @deprecated use DeliveryRate */
|
|
23
|
+
export type ShipBubbleRate = DeliveryRate;
|
|
24
|
+
export interface SelectedShipping {
|
|
25
|
+
providerKey: string;
|
|
26
|
+
serviceCode: string;
|
|
27
|
+
courierId: string;
|
|
28
|
+
requestToken: string;
|
|
29
|
+
quoteId: string;
|
|
30
|
+
price: number;
|
|
31
|
+
currency: string;
|
|
32
|
+
courierName: string;
|
|
33
|
+
}
|
|
34
|
+
interface ShippingOptionsPanelProps {
|
|
35
|
+
storeId: string;
|
|
36
|
+
address: ShippingQuoteAddress;
|
|
37
|
+
packages: Array<{
|
|
38
|
+
weight: number;
|
|
39
|
+
value?: number;
|
|
40
|
+
name?: string;
|
|
41
|
+
quantity?: number;
|
|
42
|
+
}>;
|
|
43
|
+
onSelect: (selection: SelectedShipping | null) => void;
|
|
44
|
+
selected: SelectedShipping | null;
|
|
45
|
+
customerInfo?: {
|
|
46
|
+
firstName?: string;
|
|
47
|
+
lastName?: string;
|
|
48
|
+
phone?: string;
|
|
49
|
+
email?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function ShippingOptionsPanel({ storeId, address, packages, onSelect, selected, customerInfo, }: ShippingOptionsPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=ShippingOptionsPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShippingOptionsPanel.d.ts","sourceRoot":"","sources":["../../../src/components/checkout/ShippingOptionsPanel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,mCAAmC;AACnC,MAAM,MAAM,cAAc,GAAG,YAAY,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;IACvD,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAkBD,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,EAAE,yBAAyB,2CAuK3B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { api } from '../../lib/api.js';
|
|
5
|
+
import { Skeleton } from '../../components/ui/skeleton.js';
|
|
6
|
+
import { buildDeliveryQuotePayload } from '../../lib/shipping-address.util.js';
|
|
7
|
+
function toSelectedShipping(rate) {
|
|
8
|
+
const serviceCode = rate.serviceCode || rate.methodCode || 'standard';
|
|
9
|
+
const courierId = rate.courierId || '';
|
|
10
|
+
const quoteToken = rate.quoteToken || '';
|
|
11
|
+
return {
|
|
12
|
+
providerKey: rate.providerKey,
|
|
13
|
+
serviceCode,
|
|
14
|
+
courierId,
|
|
15
|
+
requestToken: quoteToken,
|
|
16
|
+
quoteId: rate.providerKey === 'shipbubble' ? `${quoteToken}|${courierId}` : quoteToken,
|
|
17
|
+
price: rate.price,
|
|
18
|
+
currency: rate.currency,
|
|
19
|
+
courierName: rate.methodName,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function ShippingOptionsPanel({ storeId, address, packages, onSelect, selected, customerInfo, }) {
|
|
23
|
+
const [rates, setRates] = useState([]);
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState(null);
|
|
26
|
+
const [addressIncomplete, setAddressIncomplete] = useState(false);
|
|
27
|
+
const fetchRates = useCallback(async () => {
|
|
28
|
+
if (!address.city || !address.state || !address.country) {
|
|
29
|
+
setAddressIncomplete(true);
|
|
30
|
+
setRates([]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const street = (address.street || address.address1 || '').trim();
|
|
34
|
+
if (!street) {
|
|
35
|
+
setAddressIncomplete(true);
|
|
36
|
+
setRates([]);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setAddressIncomplete(false);
|
|
40
|
+
setLoading(true);
|
|
41
|
+
setError(null);
|
|
42
|
+
try {
|
|
43
|
+
const payload = buildDeliveryQuotePayload({
|
|
44
|
+
storeId,
|
|
45
|
+
address,
|
|
46
|
+
packages,
|
|
47
|
+
customerInfo,
|
|
48
|
+
});
|
|
49
|
+
const response = await api.post('/delivery/quote', payload);
|
|
50
|
+
const quotes = response.data?.data?.quotes || response.data?.data || [];
|
|
51
|
+
setRates(quotes);
|
|
52
|
+
if (quotes.length > 0 && !selected) {
|
|
53
|
+
const recommended = quotes.find((q) => q.isRecommended || q.isFastest) || quotes[0];
|
|
54
|
+
onSelect(toSelectedShipping(recommended));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const message = err?.response?.data?.message ||
|
|
59
|
+
err?.message ||
|
|
60
|
+
'Could not load shipping options. You can continue and shipping will be calculated at fulfilment.';
|
|
61
|
+
setError(message);
|
|
62
|
+
setRates([]);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
setLoading(false);
|
|
66
|
+
}
|
|
67
|
+
}, [
|
|
68
|
+
storeId,
|
|
69
|
+
address.city,
|
|
70
|
+
address.state,
|
|
71
|
+
address.country,
|
|
72
|
+
address.street,
|
|
73
|
+
address.address1,
|
|
74
|
+
address.zipCode,
|
|
75
|
+
packages,
|
|
76
|
+
customerInfo,
|
|
77
|
+
selected,
|
|
78
|
+
onSelect,
|
|
79
|
+
]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
fetchRates();
|
|
82
|
+
}, [fetchRates]);
|
|
83
|
+
const handleSelect = (rate) => {
|
|
84
|
+
onSelect(toSelectedShipping(rate));
|
|
85
|
+
};
|
|
86
|
+
if (addressIncomplete) {
|
|
87
|
+
return (_jsx("p", { className: "text-xs text-gray-500 bg-gray-50 border border-gray-200 rounded px-3 py-2", children: "Complete your address (street, city, state, country) to see shipping rates." }));
|
|
88
|
+
}
|
|
89
|
+
if (loading) {
|
|
90
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium text-gray-700", children: "Shipping options" }), _jsx(Skeleton, { className: "h-16 w-full" }), _jsx(Skeleton, { className: "h-16 w-full" })] }));
|
|
91
|
+
}
|
|
92
|
+
if (error) {
|
|
93
|
+
return (_jsx("p", { className: "text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded px-3 py-2", children: error }));
|
|
94
|
+
}
|
|
95
|
+
if (rates.length === 0) {
|
|
96
|
+
return (_jsx("p", { className: "text-xs text-gray-500 bg-gray-50 border border-gray-200 rounded px-3 py-2", children: "No courier rates available for this address. Try a different address or continue \u2014 shipping may be arranged after checkout." }));
|
|
97
|
+
}
|
|
98
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm font-medium text-gray-700", children: "Shipping options" }), _jsx("div", { className: "space-y-2", children: rates.map((rate, idx) => {
|
|
99
|
+
const serviceCode = rate.serviceCode || rate.methodCode || 'standard';
|
|
100
|
+
const courierId = rate.courierId || '';
|
|
101
|
+
const isSelected = selected?.providerKey === rate.providerKey &&
|
|
102
|
+
selected?.serviceCode === serviceCode &&
|
|
103
|
+
selected?.courierId === courierId;
|
|
104
|
+
const etaDays = rate.etaTo;
|
|
105
|
+
return (_jsxs("button", { type: "button", onClick: () => handleSelect(rate), className: [
|
|
106
|
+
'w-full flex items-start gap-3 p-3 rounded-lg border text-left transition-colors',
|
|
107
|
+
isSelected
|
|
108
|
+
? 'border-blue-500 bg-blue-50'
|
|
109
|
+
: 'border-gray-200 hover:border-gray-300 bg-white',
|
|
110
|
+
].join(' '), children: [_jsx("span", { className: [
|
|
111
|
+
'mt-0.5 flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center',
|
|
112
|
+
isSelected ? 'border-blue-500' : 'border-gray-300',
|
|
113
|
+
].join(' '), children: isSelected && _jsx("span", { className: "w-2 h-2 rounded-full bg-blue-500" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "text-sm font-medium text-gray-900", children: rate.methodName }), rate.isFastest && (_jsx("span", { className: "text-[10px] font-semibold bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded-full uppercase tracking-wide", children: "Fastest" })), rate.isCheapest && (_jsx("span", { className: "text-[10px] font-semibold bg-green-100 text-green-700 px-1.5 py-0.5 rounded-full uppercase tracking-wide", children: "Cheapest" }))] }), _jsxs("div", { className: "flex items-center gap-2 mt-0.5 text-xs text-gray-500 flex-wrap", children: [etaDays && _jsxs("span", { children: ["Arrives in ~", etaDays, " day", etaDays !== 1 ? 's' : ''] }), rate.providerKey === 'shipbubble' && rate.serviceType === 'dropoff' && _jsx("span", { children: "\u00B7 Drop-off at courier station" }), rate.trackingLevel != null && _jsxs("span", { children: ["\u00B7 Tracking ", rate.trackingLevel, "/10"] })] })] }), _jsx("div", { className: "flex-shrink-0 text-right", children: _jsxs("p", { className: "text-sm font-semibold text-gray-900", children: [rate.currency, " ", rate.price.toLocaleString()] }) })] }, `${rate.providerKey}-${serviceCode}-${courierId}-${idx}`));
|
|
114
|
+
}) })] }));
|
|
115
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guest-checkout-modal.d.ts","sourceRoot":"","sources":["../../../src/components/ui/guest-checkout-modal.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,IAAI,CAAC;IACX,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,GACZ,EAAE,uBAAuB,
|
|
1
|
+
{"version":3,"file":"guest-checkout-modal.d.ts","sourceRoot":"","sources":["../../../src/components/ui/guest-checkout-modal.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,IAAI,CAAC;IACX,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,GACZ,EAAE,uBAAuB,2CAoRzB"}
|
|
@@ -35,8 +35,11 @@ export function GuestCheckoutModal({ isOpen, onClose, onSubmit, initialData, })
|
|
|
35
35
|
if (!formData.phone.trim()) {
|
|
36
36
|
newErrors.phone = 'Phone number is required';
|
|
37
37
|
}
|
|
38
|
-
else
|
|
39
|
-
|
|
38
|
+
else {
|
|
39
|
+
const digits = formData.phone.trim().replace(/\D/g, '');
|
|
40
|
+
if (digits.length < 7 || digits.length > 15) {
|
|
41
|
+
newErrors.phone = 'Please enter a valid phone number';
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
if (!formData.email.trim()) {
|
|
42
45
|
newErrors.email = 'Email is required';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/use-payaza-checkout.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,oBAAoB,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"use-payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/use-payaza-checkout.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,oBAAoB,EAAE,sBAAsB,EAA6B,MAAM,uBAAuB,CAAC;AAExI,UAAU,wBAAwB;IAChC,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,SAAc,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,wBAAwB;uBAiBzE,IAAI,CAAC,oBAAoB,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;;;EAqGhG"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { initiatePayazaCheckout } from '../lib/payaza-checkout.js';
|
|
3
|
+
import { initiatePayazaCheckout, teardownPayazaCheckoutDom } from '../lib/payaza-checkout.js';
|
|
4
4
|
export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose }) {
|
|
5
5
|
const [isLoading, setIsLoading] = useState(false);
|
|
6
6
|
const [error, setError] = useState(null);
|
|
@@ -29,29 +29,7 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
|
|
|
29
29
|
};
|
|
30
30
|
const PAYAZA_TIMEOUT_MS = 45000;
|
|
31
31
|
const removePayazaOverlay = () => {
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
const iframe = document.querySelector('iframe[src*="payaza.africa"]');
|
|
35
|
-
let node = iframe;
|
|
36
|
-
while (node?.parentElement && node.parentElement !== document.body) {
|
|
37
|
-
node = node.parentElement;
|
|
38
|
-
}
|
|
39
|
-
if (node?.parentElement === document.body) {
|
|
40
|
-
node.remove();
|
|
41
|
-
}
|
|
42
|
-
else if (iframe?.parentElement) {
|
|
43
|
-
iframe.parentElement.remove();
|
|
44
|
-
}
|
|
45
|
-
document.querySelectorAll('style').forEach((styleEl) => {
|
|
46
|
-
if (styleEl.textContent?.includes('payaza-loading-spinner')) {
|
|
47
|
-
styleEl.remove();
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
document.querySelectorAll('div').forEach((div) => {
|
|
51
|
-
if (div.innerHTML.includes('payaza-loading-spinner')) {
|
|
52
|
-
div.remove();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
32
|
+
teardownPayazaCheckoutDom();
|
|
55
33
|
};
|
|
56
34
|
const setLoadingTimeout = () => {
|
|
57
35
|
timeoutRef.current = setTimeout(() => {
|
|
@@ -87,12 +65,14 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
|
|
|
87
65
|
clearLoadingWithTimeout();
|
|
88
66
|
setIsLoading(false);
|
|
89
67
|
setError(errorMessage);
|
|
68
|
+
removePayazaOverlay();
|
|
90
69
|
onError?.(errorMessage);
|
|
91
70
|
},
|
|
92
71
|
onClose: () => {
|
|
93
72
|
callbackFired = true;
|
|
94
73
|
clearLoadingWithTimeout();
|
|
95
74
|
setIsLoading(false);
|
|
75
|
+
removePayazaOverlay();
|
|
96
76
|
onClose?.();
|
|
97
77
|
},
|
|
98
78
|
});
|
|
@@ -121,6 +101,7 @@ export function usePayazaCheckout({ publicKey = '', onSuccess, onError, onClose
|
|
|
121
101
|
const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';
|
|
122
102
|
setIsLoading(false);
|
|
123
103
|
setError(errorMessage);
|
|
104
|
+
removePayazaOverlay();
|
|
124
105
|
onError?.(errorMessage);
|
|
125
106
|
}
|
|
126
107
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/shared/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../../../src/layouts/shared/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA+BhD,UAAU,iBAAiB;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAUD,wBAAgB,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,iBAAiB,2CA2xD9D"}
|
|
@@ -4,7 +4,7 @@ import { Button } from '../../../components/ui/button.js';
|
|
|
4
4
|
import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
6
|
import Image from 'next/image';
|
|
7
|
-
import { useState, useEffect, useRef } from 'react';
|
|
7
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
8
8
|
import { cn } from '../../../lib/utils/index.js';
|
|
9
9
|
import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout.js';
|
|
10
10
|
import { generateCheckoutConfig } from '../../../lib/payaza-checkout.js';
|
|
@@ -20,10 +20,11 @@ import { AddressSelector } from '../../../components/ui/address-selector.js';
|
|
|
20
20
|
import { countries, getCountryByName, getCitiesByCountry } from '../../../lib/countries.js';
|
|
21
21
|
import { calculateVAT, getVATLabel } from '../../../lib/utils/fee-calculations.js';
|
|
22
22
|
import { shippingService } from '../../../lib/services/shipping.service.js';
|
|
23
|
-
import { enrichShippingQuoteAddress, buildShippingCalculatePayload, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
|
|
23
|
+
import { enrichShippingQuoteAddress, buildShippingCalculatePayload, canRequestShippingQuote, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
|
|
24
|
+
import { ShippingOptionsPanel } from '../../../components/checkout/ShippingOptionsPanel.js';
|
|
24
25
|
import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton.js';
|
|
25
26
|
import { useAnalytics } from '../../../hooks/use-analytics.js';
|
|
26
|
-
import { formatPhoneInput, validatePhone, getPhonePlaceholder,
|
|
27
|
+
import { formatPhoneInput, validatePhone, getPhonePlaceholder, normalizePhoneForPayaza } from '../../../lib/utils/phone-format.js';
|
|
27
28
|
const PROMO_CODE_ENABLED = false;
|
|
28
29
|
function RequiredLabel({ children }) {
|
|
29
30
|
return (_jsxs("label", { className: "text-sm font-bold text-gray-700 ml-1", children: [children, " ", _jsx("span", { className: "text-red-500", children: "*" })] }));
|
|
@@ -104,6 +105,9 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
104
105
|
const [shippingError, setShippingError] = useState(null);
|
|
105
106
|
const [freeShippingEligible, setFreeShippingEligible] = useState(false);
|
|
106
107
|
const [freeShippingThreshold, setFreeShippingThreshold] = useState(undefined);
|
|
108
|
+
const [isCourierMode, setIsCourierMode] = useState(false);
|
|
109
|
+
const [checkingDeliveryStatus, setCheckingDeliveryStatus] = useState(false);
|
|
110
|
+
const [selectedCourier, setSelectedCourier] = useState(null);
|
|
107
111
|
// Payaza Configuration
|
|
108
112
|
// Fallback key only — the authoritative key is resolved per-currency by the
|
|
109
113
|
// backend from the database-managed registry and returned by /checkout/process
|
|
@@ -235,8 +239,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
235
239
|
setLoading(false);
|
|
236
240
|
},
|
|
237
241
|
onClose: () => {
|
|
238
|
-
// onClose is only called when payment was not successful (user canceled)
|
|
239
|
-
// This is now handled correctly by payaza-checkout.ts tracking payment success state
|
|
240
242
|
addToast('Payment canceled', 'info');
|
|
241
243
|
setLoading(false);
|
|
242
244
|
},
|
|
@@ -380,9 +382,19 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
380
382
|
}
|
|
381
383
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
382
384
|
}, [isAuthenticated, user, selectedAddressId, savedAddresses.length, loadingAddresses]);
|
|
383
|
-
// Calculate shipping when address is filled
|
|
385
|
+
// Calculate legacy shipping when address is filled (flat-rate / zone methods)
|
|
384
386
|
const calculateShipping = async () => {
|
|
385
|
-
|
|
387
|
+
const quoteAddress = enrichShippingQuoteAddress({
|
|
388
|
+
street: formData.address,
|
|
389
|
+
address1: formData.address,
|
|
390
|
+
city: formData.city,
|
|
391
|
+
state: formData.state || formData.city,
|
|
392
|
+
zipCode: formData.postalCode,
|
|
393
|
+
country: formData.country,
|
|
394
|
+
phone: formData.phone,
|
|
395
|
+
email: formData.email,
|
|
396
|
+
});
|
|
397
|
+
if (!canRequestShippingQuote(quoteAddress) || checkoutItems.length === 0) {
|
|
386
398
|
setShippingMethods([]);
|
|
387
399
|
setSelectedShippingMethod(null);
|
|
388
400
|
return;
|
|
@@ -392,16 +404,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
392
404
|
try {
|
|
393
405
|
const result = await shippingService.calculateShipping(buildShippingCalculatePayload({
|
|
394
406
|
storeId: storeConfig.id,
|
|
395
|
-
address:
|
|
396
|
-
street: formData.address,
|
|
397
|
-
address1: formData.address,
|
|
398
|
-
city: formData.city,
|
|
399
|
-
state: formData.state || formData.city,
|
|
400
|
-
zipCode: formData.postalCode,
|
|
401
|
-
country: formData.country,
|
|
402
|
-
phone: formData.phone,
|
|
403
|
-
email: formData.email,
|
|
404
|
-
}),
|
|
407
|
+
address: quoteAddress,
|
|
405
408
|
customerInfo: {
|
|
406
409
|
firstName: formData.firstName,
|
|
407
410
|
lastName: formData.lastName,
|
|
@@ -510,20 +513,78 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
510
513
|
const [countrySearch, setCountrySearch] = useState('');
|
|
511
514
|
const [citySearch, setCitySearch] = useState('');
|
|
512
515
|
const deliveryEnabled = storeConfig.features?.delivery !== false;
|
|
513
|
-
|
|
516
|
+
const buildQuoteAddress = () => enrichShippingQuoteAddress({
|
|
517
|
+
street: formData.address,
|
|
518
|
+
address1: formData.address,
|
|
519
|
+
city: formData.city,
|
|
520
|
+
state: formData.state || formData.city,
|
|
521
|
+
zipCode: formData.postalCode,
|
|
522
|
+
country: formData.country,
|
|
523
|
+
phone: formData.phone,
|
|
524
|
+
email: formData.email,
|
|
525
|
+
});
|
|
526
|
+
const handleCourierSelect = useCallback((selection) => {
|
|
527
|
+
setSelectedCourier(selection);
|
|
528
|
+
if (!selection) {
|
|
529
|
+
setSelectedShippingMethod(null);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
setSelectedShippingMethod({
|
|
533
|
+
code: selection.serviceCode,
|
|
534
|
+
name: selection.courierName,
|
|
535
|
+
cost: selection.price,
|
|
536
|
+
estimatedDays: 0,
|
|
537
|
+
providerKey: selection.providerKey,
|
|
538
|
+
deliveryMethodId: selection.serviceCode,
|
|
539
|
+
deliveryQuoteId: selection.quoteId,
|
|
540
|
+
});
|
|
541
|
+
}, []);
|
|
542
|
+
// Hybrid shipping: courier quotes when providers enabled, else legacy calculate
|
|
514
543
|
useEffect(() => {
|
|
515
|
-
if (!deliveryEnabled ||
|
|
544
|
+
if (!deliveryEnabled || checkoutItems.length === 0) {
|
|
545
|
+
setIsCourierMode(false);
|
|
546
|
+
setShippingMethods([]);
|
|
547
|
+
setSelectedShippingMethod(null);
|
|
548
|
+
setSelectedCourier(null);
|
|
516
549
|
return;
|
|
517
550
|
}
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
551
|
+
const quoteAddress = buildQuoteAddress();
|
|
552
|
+
if (!canRequestShippingQuote(quoteAddress)) {
|
|
553
|
+
setIsCourierMode(false);
|
|
554
|
+
setShippingMethods([]);
|
|
555
|
+
setSelectedShippingMethod(null);
|
|
556
|
+
setSelectedCourier(null);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const timer = setTimeout(async () => {
|
|
560
|
+
setCheckingDeliveryStatus(true);
|
|
561
|
+
setShippingError(null);
|
|
562
|
+
try {
|
|
563
|
+
const status = await shippingService.getDeliveryStatus(storeConfig.id);
|
|
564
|
+
const anyProvider = status.enabled && Object.values(status.providers || {}).some(Boolean);
|
|
565
|
+
setIsCourierMode(anyProvider);
|
|
566
|
+
if (anyProvider) {
|
|
567
|
+
setShippingMethods([]);
|
|
568
|
+
setSelectedShippingMethod(null);
|
|
569
|
+
setSelectedCourier(null);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
await calculateShipping();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
setIsCourierMode(false);
|
|
577
|
+
await calculateShipping();
|
|
578
|
+
}
|
|
579
|
+
finally {
|
|
580
|
+
setCheckingDeliveryStatus(false);
|
|
581
|
+
}
|
|
582
|
+
}, 500);
|
|
521
583
|
return () => clearTimeout(timer);
|
|
522
584
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
523
|
-
}, [deliveryEnabled, formData.address, formData.city, formData.state, formData.country, formData.postalCode, formData.email, formData.phone, formData.firstName, formData.lastName, checkoutItems.length, subtotal, currency]);
|
|
585
|
+
}, [deliveryEnabled, formData.address, formData.city, formData.state, formData.country, formData.postalCode, formData.email, formData.phone, formData.firstName, formData.lastName, checkoutItems.length, subtotal, currency, storeConfig.id]);
|
|
524
586
|
const validateForm = () => {
|
|
525
587
|
const newErrors = {};
|
|
526
|
-
const phoneCountryCode = toCountryCode(getCountryByName(formData.country)?.code);
|
|
527
588
|
if (!formData.email)
|
|
528
589
|
newErrors.email = 'Email is required';
|
|
529
590
|
else if (!/\S+@\S+\.\S+/.test(formData.email))
|
|
@@ -543,7 +604,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
543
604
|
}
|
|
544
605
|
if (!formData.phone)
|
|
545
606
|
newErrors.phone = 'Phone is required';
|
|
546
|
-
else if (!validatePhone(formData.phone
|
|
607
|
+
else if (!validatePhone(formData.phone)) {
|
|
547
608
|
newErrors.phone = 'Please enter a valid phone number';
|
|
548
609
|
}
|
|
549
610
|
setErrors(newErrors);
|
|
@@ -557,10 +618,9 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
557
618
|
setIsPromoApplied(true);
|
|
558
619
|
addToast('Promo code applied successfully!', 'success');
|
|
559
620
|
};
|
|
560
|
-
const
|
|
561
|
-
const phonePlaceholder = getPhonePlaceholder(phoneCountryCode);
|
|
621
|
+
const phonePlaceholder = getPhonePlaceholder();
|
|
562
622
|
const handlePhoneChange = (value) => {
|
|
563
|
-
setFormData({ ...formData, phone: formatPhoneInput(value
|
|
623
|
+
setFormData({ ...formData, phone: formatPhoneInput(value) });
|
|
564
624
|
};
|
|
565
625
|
// Handle address selection
|
|
566
626
|
const handleAddressSelect = (addressId) => {
|
|
@@ -630,6 +690,12 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
630
690
|
if (!formData.phone) {
|
|
631
691
|
return { isValid: false, error: 'Phone is required' };
|
|
632
692
|
}
|
|
693
|
+
if (!validatePhone(formData.phone)) {
|
|
694
|
+
return { isValid: false, error: 'Please enter a valid phone number' };
|
|
695
|
+
}
|
|
696
|
+
if (isNigeria(formData.country) && !formData.state) {
|
|
697
|
+
return { isValid: false, error: 'State is required for Nigeria' };
|
|
698
|
+
}
|
|
633
699
|
// Validate currency availability
|
|
634
700
|
const hasCurrency = storeConfig.settings?.currency || checkoutItems.some(item => item.product.currency);
|
|
635
701
|
if (!hasCurrency) {
|
|
@@ -766,7 +832,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
766
832
|
checkoutConfig = generateCheckoutConfig(payazaItems, formData.email, effectivePayazaPublicKey, {
|
|
767
833
|
firstName: formData.firstName,
|
|
768
834
|
lastName: formData.lastName,
|
|
769
|
-
phone: formData.phone,
|
|
835
|
+
phone: normalizePhoneForPayaza(formData.phone),
|
|
770
836
|
callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}`,
|
|
771
837
|
currency: checkoutResponse.currency || storeConfig.settings?.currency,
|
|
772
838
|
storeId: storeConfig.id,
|
|
@@ -920,9 +986,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
920
986
|
country: country.name,
|
|
921
987
|
city: '',
|
|
922
988
|
state: '',
|
|
923
|
-
phone: prev.phone
|
|
924
|
-
? formatPhoneInput(prev.phone, toCountryCode(country.code))
|
|
925
|
-
: prev.phone,
|
|
926
989
|
}));
|
|
927
990
|
setIsCountryDropdownOpen(false);
|
|
928
991
|
setCountrySearch('');
|
|
@@ -951,9 +1014,19 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
951
1014
|
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.city === city && "bg-gray-50 font-medium"), children: city }, city))), getCitiesByCountry(formData.country).filter(c => c.toLowerCase().includes(citySearch.toLowerCase())).length === 0 && (_jsx("div", { className: "px-5 py-3 text-gray-400 text-sm", children: "No cities found" }))] })] })) : (_jsx("div", { className: "px-5 py-3 text-gray-500 text-sm", children: "No cities available. Please type your city in the field above." })) })] }))] }), errors.city && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })] }), isNigeria(formData.country) && (_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "State" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsStateDropdownOpen(!isStateDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.state ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: formData.state ? "text-gray-900" : "text-gray-400", children: formData.state || 'Select state' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isStateDropdownOpen && "transform rotate-180") })] }), isStateDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsStateDropdownOpen(false) }), _jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: NIGERIAN_STATES.map((state) => (_jsx("button", { type: "button", onClick: () => {
|
|
952
1015
|
setFormData({ ...formData, state });
|
|
953
1016
|
setIsStateDropdownOpen(false);
|
|
954
|
-
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && storeConfig.settings?.shippingEnabled !== false && (
|
|
1017
|
+
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && storeConfig.settings?.shippingEnabled !== false && canRequestShippingQuote(buildQuoteAddress()) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), !isCourierMode && freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
|
|
955
1018
|
width: `${Math.min(100, (subtotal / freeShippingThreshold) * 100)}%`,
|
|
956
|
-
} }) })] })), isCalculatingShipping ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) :
|
|
1019
|
+
} }) })] })), checkingDeliveryStatus || (!isCourierMode && isCalculatingShipping) ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : isCourierMode ? (_jsx(ShippingOptionsPanel, { storeId: storeConfig.id, address: buildQuoteAddress(), packages: checkoutItems.map((item) => ({
|
|
1020
|
+
weight: item.product?.weight || 0.5,
|
|
1021
|
+
value: item.price,
|
|
1022
|
+
name: item.product?.name,
|
|
1023
|
+
quantity: item.quantity,
|
|
1024
|
+
})), customerInfo: {
|
|
1025
|
+
firstName: formData.firstName,
|
|
1026
|
+
lastName: formData.lastName,
|
|
1027
|
+
phone: formData.phone,
|
|
1028
|
+
email: formData.email,
|
|
1029
|
+
}, onSelect: handleCourierSelect, selected: selectedCourier })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
|
|
957
1030
|
? "border-black bg-gray-50"
|
|
958
1031
|
: "border-gray-200 hover:border-gray-300 bg-white"), children: [_jsxs("div", { className: "flex items-center gap-4 flex-1", children: [_jsx("input", { type: "radio", name: "shippingMethod", value: method.code, checked: selectedShippingMethod?.code === method.code, onChange: () => setSelectedShippingMethod(method), className: "w-5 h-5 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "font-bold text-gray-900", children: method.name }), freeShippingEligible && method.cost === 0 && (_jsx("span", { className: "text-xs font-normal text-green-700 bg-green-100 px-2 py-0.5 rounded-full", children: "FREE" }))] }), method.description && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.description })), method.estimatedDays && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.estimatedDaysMin && method.estimatedDaysMax
|
|
959
1032
|
? `Estimated delivery: ${method.estimatedDaysMin}-${method.estimatedDaysMax} business days`
|
|
@@ -88,6 +88,12 @@ export declare function getCurrencyFromItems(items: Array<{
|
|
|
88
88
|
currency?: string;
|
|
89
89
|
};
|
|
90
90
|
}>, fallbackCurrency?: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Remove all Payaza SDK DOM nodes (loader overlay + invisible iframe).
|
|
93
|
+
* The SDK appends a fullscreen fixed iframe at max z-index; if checkout never
|
|
94
|
+
* loads, only the spinner is removed and the invisible iframe blocks all clicks.
|
|
95
|
+
*/
|
|
96
|
+
export declare function teardownPayazaCheckoutDom(): void;
|
|
91
97
|
/**
|
|
92
98
|
* Initialize Payaza checkout using SDK
|
|
93
99
|
* Uses setup() function from payaza-web-sdk npm package as per official documentation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/lib/payaza-checkout.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"payaza-checkout.d.ts","sourceRoot":"","sources":["../../src/lib/payaza-checkout.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Method: setup(config).showPopup()
|
|
7
7
|
*/
|
|
8
8
|
import PayazaCheckout from 'payaza-web-sdk';
|
|
9
|
+
import { normalizePhoneForPayaza } from '../lib/utils/phone-format.js';
|
|
9
10
|
export function validateAndNormalizeCurrency(currency) {
|
|
10
11
|
if (!currency || typeof currency !== 'string') {
|
|
11
12
|
throw new Error('Currency is required and must be a string');
|
|
@@ -78,6 +79,31 @@ export function getCurrencyFromItems(items, fallbackCurrency) {
|
|
|
78
79
|
const mostCommonCurrency = Object.entries(currencyCounts).reduce((a, b) => currencyCounts[a[0]] > currencyCounts[b[0]] ? a : b)[0];
|
|
79
80
|
return mostCommonCurrency;
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove all Payaza SDK DOM nodes (loader overlay + invisible iframe).
|
|
84
|
+
* The SDK appends a fullscreen fixed iframe at max z-index; if checkout never
|
|
85
|
+
* loads, only the spinner is removed and the invisible iframe blocks all clicks.
|
|
86
|
+
*/
|
|
87
|
+
export function teardownPayazaCheckoutDom() {
|
|
88
|
+
if (typeof document === 'undefined' || !document.body)
|
|
89
|
+
return;
|
|
90
|
+
document.querySelectorAll('style').forEach((styleEl) => {
|
|
91
|
+
if (styleEl.textContent?.includes('payaza-loading-spinner')) {
|
|
92
|
+
styleEl.remove();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
document.querySelectorAll('div').forEach((div) => {
|
|
96
|
+
if (div.innerHTML.includes('payaza-loading-spinner')) {
|
|
97
|
+
div.remove();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
document.querySelectorAll('iframe[src*="payaza.africa"], iframe[src*="shopaza.africa"]').forEach((iframe) => {
|
|
101
|
+
iframe.remove();
|
|
102
|
+
});
|
|
103
|
+
if (document.body.style.overflow === 'hidden') {
|
|
104
|
+
document.body.style.overflow = '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
81
107
|
/**
|
|
82
108
|
* Initialize Payaza checkout using SDK
|
|
83
109
|
* Uses setup() function from payaza-web-sdk npm package as per official documentation
|
|
@@ -98,6 +124,19 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
98
124
|
}
|
|
99
125
|
console.log('[Payaza] Initiating checkout with npm package...');
|
|
100
126
|
console.log('[Payaza] SDK verified ready, proceeding with checkout setup');
|
|
127
|
+
if (!Number.isFinite(config.amount) || config.amount <= 0) {
|
|
128
|
+
throw new Error('Invalid payment amount. Please refresh and try again.');
|
|
129
|
+
}
|
|
130
|
+
if (!config.email?.trim()) {
|
|
131
|
+
throw new Error('Email is required for payment.');
|
|
132
|
+
}
|
|
133
|
+
if (!config.firstName?.trim()) {
|
|
134
|
+
throw new Error('First name is required for payment.');
|
|
135
|
+
}
|
|
136
|
+
if (!config.lastName?.trim()) {
|
|
137
|
+
throw new Error('Last name is required for payment.');
|
|
138
|
+
}
|
|
139
|
+
const normalizedPhone = config.phone ? normalizePhoneForPayaza(config.phone) : '';
|
|
101
140
|
// Generate transaction reference
|
|
102
141
|
const reference = config.reference || `PL${Math.floor((Math.random() * 10000000) + 1)}`;
|
|
103
142
|
console.log('[Payaza] Transaction reference:', reference);
|
|
@@ -141,8 +180,8 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
141
180
|
if (config.lastName) {
|
|
142
181
|
additionalDetails.customerLastName = config.lastName;
|
|
143
182
|
}
|
|
144
|
-
if (
|
|
145
|
-
additionalDetails.customerPhone =
|
|
183
|
+
if (normalizedPhone) {
|
|
184
|
+
additionalDetails.customerPhone = normalizedPhone;
|
|
146
185
|
}
|
|
147
186
|
// Shipping address information
|
|
148
187
|
if (config.shippingAddress) {
|
|
@@ -176,7 +215,7 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
176
215
|
email_address: config.email,
|
|
177
216
|
first_name: config.firstName || '', // Required field
|
|
178
217
|
last_name: config.lastName || '', // Required field
|
|
179
|
-
phone_number:
|
|
218
|
+
phone_number: normalizedPhone,
|
|
180
219
|
transaction_reference: reference,
|
|
181
220
|
// Required by interface, but we'll override with setCallback/setOnClose
|
|
182
221
|
onClose: () => { },
|
|
@@ -198,18 +237,7 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
198
237
|
let loaderTimeoutId = null;
|
|
199
238
|
let checkoutLoadedFired = false;
|
|
200
239
|
const removePayazaLoaderDom = () => {
|
|
201
|
-
|
|
202
|
-
return;
|
|
203
|
-
document.querySelectorAll('style').forEach((styleEl) => {
|
|
204
|
-
if (styleEl.textContent?.includes('payaza-loading-spinner')) {
|
|
205
|
-
styleEl.remove();
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
document.querySelectorAll('div').forEach((div) => {
|
|
209
|
-
if (div.innerHTML.includes('payaza-loading-spinner')) {
|
|
210
|
-
div.remove();
|
|
211
|
-
}
|
|
212
|
-
});
|
|
240
|
+
teardownPayazaCheckoutDom();
|
|
213
241
|
};
|
|
214
242
|
const clearLoaderTimeout = () => {
|
|
215
243
|
if (loaderTimeoutId) {
|
|
@@ -297,12 +325,13 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
297
325
|
message: errorMessage,
|
|
298
326
|
data: errorData,
|
|
299
327
|
});
|
|
300
|
-
// Call the error handler and clear close-check interval
|
|
301
328
|
clearCloseCheckInterval();
|
|
329
|
+
clearLoaderTimeout();
|
|
330
|
+
removePayazaLoaderDom();
|
|
302
331
|
if (config.onError) {
|
|
303
332
|
config.onError(errorMessage);
|
|
304
333
|
}
|
|
305
|
-
return;
|
|
334
|
+
return;
|
|
306
335
|
}
|
|
307
336
|
// Handle success responses
|
|
308
337
|
if (isSuccessResponse && callbackResponse.data) {
|
|
@@ -371,6 +400,8 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
371
400
|
data: callbackResponse.data,
|
|
372
401
|
});
|
|
373
402
|
clearCloseCheckInterval();
|
|
403
|
+
clearLoaderTimeout();
|
|
404
|
+
removePayazaLoaderDom();
|
|
374
405
|
if (config.onError) {
|
|
375
406
|
config.onError('Received unexpected response from payment gateway. Please try again.');
|
|
376
407
|
}
|
|
@@ -407,14 +438,29 @@ export const initiatePayazaCheckout = async (config) => {
|
|
|
407
438
|
}, PAYAZA_CLOSE_CHECK_MS);
|
|
408
439
|
closeCheckIntervalId = checkLoadedAndClose;
|
|
409
440
|
loaderTimeoutId = setTimeout(() => {
|
|
410
|
-
if (closeHandled)
|
|
441
|
+
if (closeHandled || checkoutLoadedFired)
|
|
411
442
|
return;
|
|
412
|
-
|
|
443
|
+
const loaderStillVisible = hasPayazaLoaderInDom();
|
|
444
|
+
const iframePresent = Boolean(typeof document !== 'undefined' &&
|
|
445
|
+
document.querySelector('iframe[src*="payaza.africa"], iframe[src*="shopaza.africa"]'));
|
|
446
|
+
if (!loaderStillVisible && !iframePresent)
|
|
413
447
|
return;
|
|
414
|
-
console.error('[Payaza] Loader timeout: checkout overlay did not clear'
|
|
415
|
-
|
|
448
|
+
console.error('[Payaza] Loader timeout: checkout overlay did not clear', {
|
|
449
|
+
keyPrefix: config.publicKey?.substring(0, 8),
|
|
450
|
+
amount: config.amount,
|
|
451
|
+
currency: validatedCurrency,
|
|
452
|
+
connectionMode,
|
|
453
|
+
email: config.email,
|
|
454
|
+
reference,
|
|
455
|
+
loaderStillVisible,
|
|
456
|
+
iframePresent,
|
|
457
|
+
});
|
|
458
|
+
teardownPayazaCheckoutDom();
|
|
459
|
+
closeHandled = true;
|
|
416
460
|
clearCloseCheckInterval();
|
|
417
461
|
clearLoaderTimeout();
|
|
462
|
+
checkoutLoadedFired = true;
|
|
463
|
+
config.onCheckoutLoaded?.();
|
|
418
464
|
if (config.onError) {
|
|
419
465
|
config.onError('Payment window failed to load. This may be caused by browser security settings or a blocked connection to Payaza. Please try again.');
|
|
420
466
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
1
|
+
export declare function validatePhone(value: string): boolean;
|
|
2
|
+
export declare function formatPhoneInput(value: string): string;
|
|
3
|
+
export declare function getPhonePlaceholder(): string;
|
|
4
|
+
/** Strip spaces/dashes/parens before sending to Payaza SDK. Keeps + and digits. */
|
|
5
|
+
export declare function normalizePhoneForPayaza(value: string): string;
|
|
6
6
|
//# sourceMappingURL=phone-format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phone-format.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/phone-format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"phone-format.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/phone-format.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKpD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D"}
|
|
@@ -1,31 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import examples from 'libphonenumber-js/mobile/examples';
|
|
3
|
-
export function toCountryCode(isoCode) {
|
|
4
|
-
if (!isoCode || isoCode.length !== 2)
|
|
5
|
-
return 'NG';
|
|
6
|
-
return isoCode.toUpperCase();
|
|
7
|
-
}
|
|
8
|
-
export function formatPhoneInput(value, countryCode) {
|
|
9
|
-
const formatter = new AsYouType(countryCode);
|
|
10
|
-
return formatter.input(value);
|
|
11
|
-
}
|
|
12
|
-
export function validatePhone(value, countryCode) {
|
|
1
|
+
export function validatePhone(value) {
|
|
13
2
|
const trimmed = value.trim();
|
|
14
3
|
if (!trimmed)
|
|
15
4
|
return false;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
const digits = trimmed.replace(/\D/g, '');
|
|
6
|
+
return digits.length >= 7 && digits.length <= 15;
|
|
7
|
+
}
|
|
8
|
+
export function formatPhoneInput(value) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export function getPhonePlaceholder() {
|
|
12
|
+
return 'e.g. +234 801 234 5678';
|
|
22
13
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return example?.formatInternational() ?? '+234 801 234 5678';
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return '+234 801 234 5678';
|
|
30
|
-
}
|
|
14
|
+
/** Strip spaces/dashes/parens before sending to Payaza SDK. Keeps + and digits. */
|
|
15
|
+
export function normalizePhoneForPayaza(value) {
|
|
16
|
+
return value.trim().replace(/[\s\-\(\)]/g, '');
|
|
31
17
|
}
|
package/dist/styles/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! tailwindcss v4.
|
|
1
|
+
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer properties;
|
|
3
3
|
@layer theme, base, components, utilities;
|
|
4
4
|
@layer theme {
|
|
@@ -344,6 +344,9 @@
|
|
|
344
344
|
.\!visible {
|
|
345
345
|
visibility: visible !important;
|
|
346
346
|
}
|
|
347
|
+
.invisible {
|
|
348
|
+
visibility: hidden;
|
|
349
|
+
}
|
|
347
350
|
.visible {
|
|
348
351
|
visibility: visible;
|
|
349
352
|
}
|
|
@@ -428,10 +431,10 @@
|
|
|
428
431
|
top: calc(var(--spacing) * 1.5);
|
|
429
432
|
}
|
|
430
433
|
.top-1\/2 {
|
|
431
|
-
top: calc(1/2 * 100%);
|
|
434
|
+
top: calc(1 / 2 * 100%);
|
|
432
435
|
}
|
|
433
436
|
.top-1\/4 {
|
|
434
|
-
top: calc(1/4 * 100%);
|
|
437
|
+
top: calc(1 / 4 * 100%);
|
|
435
438
|
}
|
|
436
439
|
.top-2 {
|
|
437
440
|
top: calc(var(--spacing) * 2);
|
|
@@ -518,7 +521,7 @@
|
|
|
518
521
|
right: calc(var(--spacing) * 1);
|
|
519
522
|
}
|
|
520
523
|
.right-1\/4 {
|
|
521
|
-
right: calc(1/4 * 100%);
|
|
524
|
+
right: calc(1 / 4 * 100%);
|
|
522
525
|
}
|
|
523
526
|
.right-2 {
|
|
524
527
|
right: calc(var(--spacing) * 2);
|
|
@@ -578,7 +581,7 @@
|
|
|
578
581
|
bottom: calc(var(--spacing) * 1.5);
|
|
579
582
|
}
|
|
580
583
|
.bottom-1\/4 {
|
|
581
|
-
bottom: calc(1/4 * 100%);
|
|
584
|
+
bottom: calc(1 / 4 * 100%);
|
|
582
585
|
}
|
|
583
586
|
.bottom-2 {
|
|
584
587
|
bottom: calc(var(--spacing) * 2);
|
|
@@ -626,10 +629,10 @@
|
|
|
626
629
|
left: calc(var(--spacing) * 1);
|
|
627
630
|
}
|
|
628
631
|
.left-1\/2 {
|
|
629
|
-
left: calc(1/2 * 100%);
|
|
632
|
+
left: calc(1 / 2 * 100%);
|
|
630
633
|
}
|
|
631
634
|
.left-1\/4 {
|
|
632
|
-
left: calc(1/4 * 100%);
|
|
635
|
+
left: calc(1 / 4 * 100%);
|
|
633
636
|
}
|
|
634
637
|
.left-2 {
|
|
635
638
|
left: calc(var(--spacing) * 2);
|
|
@@ -1085,7 +1088,7 @@
|
|
|
1085
1088
|
height: calc(var(--spacing) * 1.5);
|
|
1086
1089
|
}
|
|
1087
1090
|
.h-1\/4 {
|
|
1088
|
-
height: calc(1/4 * 100%);
|
|
1091
|
+
height: calc(1 / 4 * 100%);
|
|
1089
1092
|
}
|
|
1090
1093
|
.h-2 {
|
|
1091
1094
|
height: calc(var(--spacing) * 2);
|
|
@@ -1100,7 +1103,7 @@
|
|
|
1100
1103
|
height: calc(var(--spacing) * 3.5);
|
|
1101
1104
|
}
|
|
1102
1105
|
.h-3\/4 {
|
|
1103
|
-
height: calc(3/4 * 100%);
|
|
1106
|
+
height: calc(3 / 4 * 100%);
|
|
1104
1107
|
}
|
|
1105
1108
|
.h-4 {
|
|
1106
1109
|
height: calc(var(--spacing) * 4);
|
|
@@ -1349,13 +1352,13 @@
|
|
|
1349
1352
|
width: calc(var(--spacing) * 1.5);
|
|
1350
1353
|
}
|
|
1351
1354
|
.w-1\/2 {
|
|
1352
|
-
width: calc(1/2 * 100%);
|
|
1355
|
+
width: calc(1 / 2 * 100%);
|
|
1353
1356
|
}
|
|
1354
1357
|
.w-1\/3 {
|
|
1355
|
-
width: calc(1/3 * 100%);
|
|
1358
|
+
width: calc(1 / 3 * 100%);
|
|
1356
1359
|
}
|
|
1357
1360
|
.w-1\/4 {
|
|
1358
|
-
width: calc(1/4 * 100%);
|
|
1361
|
+
width: calc(1 / 4 * 100%);
|
|
1359
1362
|
}
|
|
1360
1363
|
.w-2 {
|
|
1361
1364
|
width: calc(var(--spacing) * 2);
|
|
@@ -1364,10 +1367,10 @@
|
|
|
1364
1367
|
width: calc(var(--spacing) * 2.5);
|
|
1365
1368
|
}
|
|
1366
1369
|
.w-2\/3 {
|
|
1367
|
-
width: calc(2/3 * 100%);
|
|
1370
|
+
width: calc(2 / 3 * 100%);
|
|
1368
1371
|
}
|
|
1369
1372
|
.w-2\/5 {
|
|
1370
|
-
width: calc(2/5 * 100%);
|
|
1373
|
+
width: calc(2 / 5 * 100%);
|
|
1371
1374
|
}
|
|
1372
1375
|
.w-3 {
|
|
1373
1376
|
width: calc(var(--spacing) * 3);
|
|
@@ -1376,7 +1379,7 @@
|
|
|
1376
1379
|
width: calc(var(--spacing) * 3.5);
|
|
1377
1380
|
}
|
|
1378
1381
|
.w-3\/4 {
|
|
1379
|
-
width: calc(3/4 * 100%);
|
|
1382
|
+
width: calc(3 / 4 * 100%);
|
|
1380
1383
|
}
|
|
1381
1384
|
.w-4 {
|
|
1382
1385
|
width: calc(var(--spacing) * 4);
|
|
@@ -1385,13 +1388,13 @@
|
|
|
1385
1388
|
width: calc(var(--spacing) * 4.5);
|
|
1386
1389
|
}
|
|
1387
1390
|
.w-4\/6 {
|
|
1388
|
-
width: calc(4/6 * 100%);
|
|
1391
|
+
width: calc(4 / 6 * 100%);
|
|
1389
1392
|
}
|
|
1390
1393
|
.w-5 {
|
|
1391
1394
|
width: calc(var(--spacing) * 5);
|
|
1392
1395
|
}
|
|
1393
1396
|
.w-5\/6 {
|
|
1394
|
-
width: calc(5/6 * 100%);
|
|
1397
|
+
width: calc(5 / 6 * 100%);
|
|
1395
1398
|
}
|
|
1396
1399
|
.w-6 {
|
|
1397
1400
|
width: calc(var(--spacing) * 6);
|
|
@@ -1652,7 +1655,7 @@
|
|
|
1652
1655
|
transform-origin: 0;
|
|
1653
1656
|
}
|
|
1654
1657
|
.-translate-x-1\/2 {
|
|
1655
|
-
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
|
1658
|
+
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
|
|
1656
1659
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1657
1660
|
}
|
|
1658
1661
|
.-translate-x-2 {
|
|
@@ -1672,7 +1675,7 @@
|
|
|
1672
1675
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1673
1676
|
}
|
|
1674
1677
|
.translate-x-1\/2 {
|
|
1675
|
-
--tw-translate-x: calc(1/2 * 100%);
|
|
1678
|
+
--tw-translate-x: calc(1 / 2 * 100%);
|
|
1676
1679
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1677
1680
|
}
|
|
1678
1681
|
.translate-x-2 {
|
|
@@ -1708,7 +1711,7 @@
|
|
|
1708
1711
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1709
1712
|
}
|
|
1710
1713
|
.-translate-y-1\/2 {
|
|
1711
|
-
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
|
1714
|
+
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
|
|
1712
1715
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1713
1716
|
}
|
|
1714
1717
|
.-translate-y-4 {
|
|
@@ -1875,6 +1878,24 @@
|
|
|
1875
1878
|
.scroll-pt-1 {
|
|
1876
1879
|
scroll-padding-top: calc(var(--spacing) * 1);
|
|
1877
1880
|
}
|
|
1881
|
+
.scrollbar-thin {
|
|
1882
|
+
scrollbar-width: thin;
|
|
1883
|
+
}
|
|
1884
|
+
.scrollbar-thumb-gray-200 {
|
|
1885
|
+
--tw-scrollbar-thumb: var(--color-gray-200);
|
|
1886
|
+
scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
|
|
1887
|
+
}
|
|
1888
|
+
.scrollbar-thumb-white\/10 {
|
|
1889
|
+
--tw-scrollbar-thumb: color-mix(in srgb, #fff 10%, transparent);
|
|
1890
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1891
|
+
--tw-scrollbar-thumb: color-mix(in oklab, var(--color-white) 10%, transparent);
|
|
1892
|
+
}
|
|
1893
|
+
scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
|
|
1894
|
+
}
|
|
1895
|
+
.scrollbar-track-transparent {
|
|
1896
|
+
--tw-scrollbar-track: transparent;
|
|
1897
|
+
scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);
|
|
1898
|
+
}
|
|
1878
1899
|
.list-decimal {
|
|
1879
1900
|
list-style-type: decimal;
|
|
1880
1901
|
}
|
|
@@ -2458,6 +2479,9 @@
|
|
|
2458
2479
|
.border-amber-100 {
|
|
2459
2480
|
border-color: var(--color-amber-100);
|
|
2460
2481
|
}
|
|
2482
|
+
.border-amber-200 {
|
|
2483
|
+
border-color: var(--color-amber-200);
|
|
2484
|
+
}
|
|
2461
2485
|
.border-black {
|
|
2462
2486
|
border-color: var(--color-black);
|
|
2463
2487
|
}
|
|
@@ -4469,6 +4493,9 @@
|
|
|
4469
4493
|
color: color-mix(in oklab, var(--color-amber-500) 80%, transparent);
|
|
4470
4494
|
}
|
|
4471
4495
|
}
|
|
4496
|
+
.text-amber-600 {
|
|
4497
|
+
color: var(--color-amber-600);
|
|
4498
|
+
}
|
|
4472
4499
|
.text-black {
|
|
4473
4500
|
color: var(--color-black);
|
|
4474
4501
|
}
|
|
@@ -8822,12 +8849,12 @@
|
|
|
8822
8849
|
}
|
|
8823
8850
|
.lg\:w-2\/5 {
|
|
8824
8851
|
@media (width >= 64rem) {
|
|
8825
|
-
width: calc(2/5 * 100%);
|
|
8852
|
+
width: calc(2 / 5 * 100%);
|
|
8826
8853
|
}
|
|
8827
8854
|
}
|
|
8828
8855
|
.lg\:w-3\/5 {
|
|
8829
8856
|
@media (width >= 64rem) {
|
|
8830
|
-
width: calc(3/5 * 100%);
|
|
8857
|
+
width: calc(3 / 5 * 100%);
|
|
8831
8858
|
}
|
|
8832
8859
|
}
|
|
8833
8860
|
.lg\:w-72 {
|
|
@@ -9682,6 +9709,16 @@
|
|
|
9682
9709
|
inherits: false;
|
|
9683
9710
|
initial-value: proximity;
|
|
9684
9711
|
}
|
|
9712
|
+
@property --tw-scrollbar-thumb {
|
|
9713
|
+
syntax: "<color>";
|
|
9714
|
+
inherits: false;
|
|
9715
|
+
initial-value: #0000;
|
|
9716
|
+
}
|
|
9717
|
+
@property --tw-scrollbar-track {
|
|
9718
|
+
syntax: "<color>";
|
|
9719
|
+
inherits: false;
|
|
9720
|
+
initial-value: #0000;
|
|
9721
|
+
}
|
|
9685
9722
|
@property --tw-space-y-reverse {
|
|
9686
9723
|
syntax: "*";
|
|
9687
9724
|
inherits: false;
|
|
@@ -10001,6 +10038,8 @@
|
|
|
10001
10038
|
--tw-skew-x: initial;
|
|
10002
10039
|
--tw-skew-y: initial;
|
|
10003
10040
|
--tw-scroll-snap-strictness: proximity;
|
|
10041
|
+
--tw-scrollbar-thumb: #0000;
|
|
10042
|
+
--tw-scrollbar-track: #0000;
|
|
10004
10043
|
--tw-space-y-reverse: 0;
|
|
10005
10044
|
--tw-space-x-reverse: 0;
|
|
10006
10045
|
--tw-divide-y-reverse: 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payaza-storefront-layouts",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared layout components for Storefront applications",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -123,7 +123,6 @@
|
|
|
123
123
|
"class-variance-authority": "^0.7.1",
|
|
124
124
|
"clsx": "^2.1.1",
|
|
125
125
|
"isomorphic-dompurify": "^2.28.0",
|
|
126
|
-
"libphonenumber-js": "^1.12.9",
|
|
127
126
|
"lucide-react": "^0.554.0",
|
|
128
127
|
"nprogress": "^0.2.0",
|
|
129
128
|
"payaza-web-sdk": "^1.0.4",
|