avada-crossapp-banner 0.0.1
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/README.md +217 -0
- package/dist/components/CrossAppBanner/CrossAppBanner.d.ts +10 -0
- package/dist/components/CrossAppBanner/CrossAppBannerWithI18n.d.ts +10 -0
- package/dist/components/CrossAppBanner/index.d.ts +3 -0
- package/dist/components/icons/CrossAppIcon.d.ts +10 -0
- package/dist/components/icons/DownloadIcon.d.ts +7 -0
- package/dist/components/icons/index.d.ts +4 -0
- package/dist/constants/index.d.ts +116 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useDisplayBanner.d.ts +66 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.esm.js +531 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +552 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +78 -0
- package/dist/types/index.d.ts +214 -0
- package/package.json +46 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
3
|
+
import { Icon, Layout, Box, InlineStack, BlockStack, Text, Button, Link } from '@shopify/polaris';
|
|
4
|
+
import { XIcon } from '@shopify/polaris-icons';
|
|
5
|
+
import { useI18n } from '@shopify/react-i18n';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* App handle constants
|
|
9
|
+
*/
|
|
10
|
+
const APP_HANDLES = {
|
|
11
|
+
ORDER_LIMIT: 'orderLimit',
|
|
12
|
+
ACCESSIBILITY: 'accessibility',
|
|
13
|
+
COOKIE_BAR: 'cookieBar',
|
|
14
|
+
AGE_VERIFICATION: 'ageVerification',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* App domains
|
|
18
|
+
*/
|
|
19
|
+
const APP_DOMAINS = {
|
|
20
|
+
COOKIE_BAR: 'cookie.avada.io',
|
|
21
|
+
AGE_VERIFICATION: 'age-verification-b0fa4.firebaseapp.com',
|
|
22
|
+
ACCESSIBILITY: 'accessibility.avada.io',
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Shopify app handles
|
|
26
|
+
*/
|
|
27
|
+
const SHOPIFY_APP_HANDLES = {
|
|
28
|
+
COOKIE_BAR: 'avada-cookie-bar',
|
|
29
|
+
AGE_VERIFICATION: 'sun-age-verification-popup',
|
|
30
|
+
ACCESSIBILITY: 'avada-accessibility',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Shopify plans to exclude from cross-app promotion
|
|
34
|
+
*/
|
|
35
|
+
const EXCLUDED_SHOPIFY_PLANS = [
|
|
36
|
+
'partner_test',
|
|
37
|
+
'affiliate',
|
|
38
|
+
'staff',
|
|
39
|
+
'frozen',
|
|
40
|
+
'fraudulent',
|
|
41
|
+
'cancelled',
|
|
42
|
+
'paused',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Countries to exclude from cross-app promotion
|
|
46
|
+
*/
|
|
47
|
+
const EXCLUDED_COUNTRIES = ['IN', 'VN'];
|
|
48
|
+
/**
|
|
49
|
+
* Email suffixes to exclude
|
|
50
|
+
*/
|
|
51
|
+
const EXCLUDED_EMAIL_SUFFIXES = ['shopify.com'];
|
|
52
|
+
/**
|
|
53
|
+
* Target storage types for display banner
|
|
54
|
+
*/
|
|
55
|
+
const TARGET_STORAGE_TYPES = {
|
|
56
|
+
NEXT_DAY: 'next_day',
|
|
57
|
+
SEVEN_DAYS: '7_days_after',
|
|
58
|
+
THIRTY_DAYS: '30_days_after',
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Get Shopify store name from domain
|
|
62
|
+
*/
|
|
63
|
+
function getShopifyName(domain) {
|
|
64
|
+
return domain.replace('.myshopify.com', '');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if shop should be excluded from cross-app promotion
|
|
68
|
+
*/
|
|
69
|
+
function shouldExcludeShop(shop) {
|
|
70
|
+
const { shopifyPlan, country, email } = shop;
|
|
71
|
+
if (shopifyPlan && EXCLUDED_SHOPIFY_PLANS.includes(shopifyPlan)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (country && EXCLUDED_COUNTRIES.includes(country)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (email) {
|
|
78
|
+
const emailLower = email.toLowerCase();
|
|
79
|
+
if (EXCLUDED_EMAIL_SUFFIXES.some(suffix => emailLower.endsWith(suffix))) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get Cookie Bar app data for cross-app promotion
|
|
87
|
+
*/
|
|
88
|
+
function getCookieBarAppData(shop, options) {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const { shopifyDomain, cookieBarInstalled } = shop;
|
|
91
|
+
const shopName = getShopifyName(shopifyDomain);
|
|
92
|
+
return {
|
|
93
|
+
imageUrl: 'https://cdnapps.avada.io/crossApp/cookieBarGift.png',
|
|
94
|
+
imageAlt: 'Cookie Bar Gift',
|
|
95
|
+
translationKey: 'CrossApp.cookieApp',
|
|
96
|
+
planUrl: '/embed/subscription',
|
|
97
|
+
targetUrl: !cookieBarInstalled
|
|
98
|
+
? `https://${APP_DOMAINS.COOKIE_BAR}/auth/shopify?shop=${shopifyDomain}`
|
|
99
|
+
: `https://admin.shopify.com/store/${shopName}/apps/${SHOPIFY_APP_HANDLES.COOKIE_BAR}/embed`,
|
|
100
|
+
showPlanBtn: (_a = options === null || options === void 0 ? void 0 : options.showPlanBtn) !== null && _a !== void 0 ? _a : false,
|
|
101
|
+
appHandle: APP_HANDLES.COOKIE_BAR,
|
|
102
|
+
bgColor: 'linear-gradient(90deg, #000926 0%, #0a1f3d 100%)',
|
|
103
|
+
isHideBanner: false,
|
|
104
|
+
eventPrefix: (_b = options === null || options === void 0 ? void 0 : options.eventPrefix) !== null && _b !== void 0 ? _b : 'cb',
|
|
105
|
+
bannerCloseKey: 'bannerCookieBarCloseCount',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get Age Verification app data for cross-app promotion
|
|
110
|
+
*/
|
|
111
|
+
function getAgeVerificationAppData(shop, options) {
|
|
112
|
+
var _a, _b;
|
|
113
|
+
const { shopifyDomain, ageVerificationInstalled, hideVerificationBanner } = shop;
|
|
114
|
+
const shopName = getShopifyName(shopifyDomain);
|
|
115
|
+
const utmSource = (_a = options === null || options === void 0 ? void 0 : options.utmSource) !== null && _a !== void 0 ? _a : 'crossapp';
|
|
116
|
+
return {
|
|
117
|
+
imageUrl: 'https://cdnapps.avada.io/crossApp/cookieBarGift.png',
|
|
118
|
+
imageAlt: 'Age Verification Person',
|
|
119
|
+
translationKey: 'CrossApp.ageVerification',
|
|
120
|
+
planUrl: '/embed/subscription',
|
|
121
|
+
targetUrl: !ageVerificationInstalled
|
|
122
|
+
? `https://apps.shopify.com/sun-age-verification-popup?utm_source=${utmSource}&utm_medium=banner&utm_campaign=dashboard`
|
|
123
|
+
: `https://admin.shopify.com/store/${shopName}/apps/${SHOPIFY_APP_HANDLES.AGE_VERIFICATION}/embed?from=${utmSource}`,
|
|
124
|
+
showPlanBtn: false,
|
|
125
|
+
appHandle: APP_HANDLES.AGE_VERIFICATION,
|
|
126
|
+
bgColor: 'linear-gradient(135deg, #060101, #0e0101, #170101, #1e0101, #290101)',
|
|
127
|
+
isHideBanner: hideVerificationBanner !== null && hideVerificationBanner !== void 0 ? hideVerificationBanner : false,
|
|
128
|
+
eventPrefix: (_b = options === null || options === void 0 ? void 0 : options.eventPrefix) !== null && _b !== void 0 ? _b : 'av',
|
|
129
|
+
bannerCloseKey: 'bannerAgeVerificationCloseCount',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get Accessibility app data for cross-app promotion
|
|
134
|
+
*/
|
|
135
|
+
function getAccessibilityAppData(shop, options) {
|
|
136
|
+
var _a;
|
|
137
|
+
const { shopifyDomain, accessibilityInstalled } = shop;
|
|
138
|
+
const shopName = getShopifyName(shopifyDomain);
|
|
139
|
+
return {
|
|
140
|
+
imageUrl: 'https://cdnapps.avada.io/crossApp/accessibilityGift.png',
|
|
141
|
+
imageAlt: 'Accessibility App',
|
|
142
|
+
translationKey: 'CrossApp.accessibilityApp',
|
|
143
|
+
planUrl: '/embed/subscription',
|
|
144
|
+
targetUrl: !accessibilityInstalled
|
|
145
|
+
? `https://${APP_DOMAINS.ACCESSIBILITY}/auth/shopify?shop=${shopifyDomain}`
|
|
146
|
+
: `https://admin.shopify.com/store/${shopName}/apps/${SHOPIFY_APP_HANDLES.ACCESSIBILITY}/embed`,
|
|
147
|
+
showPlanBtn: false,
|
|
148
|
+
appHandle: APP_HANDLES.ACCESSIBILITY,
|
|
149
|
+
bgColor: 'linear-gradient(90deg, #1a1a2e 0%, #16213e 100%)',
|
|
150
|
+
isHideBanner: false,
|
|
151
|
+
eventPrefix: (_a = options === null || options === void 0 ? void 0 : options.eventPrefix) !== null && _a !== void 0 ? _a : 'ac',
|
|
152
|
+
bannerCloseKey: 'bannerAccessibilityCloseCount',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create cross-app config helper
|
|
157
|
+
*/
|
|
158
|
+
function createCrossAppConfig(config) {
|
|
159
|
+
var _a, _b;
|
|
160
|
+
return {
|
|
161
|
+
appPrefix: config.appPrefix,
|
|
162
|
+
apiClient: config.apiClient,
|
|
163
|
+
storeDispatch: config.storeDispatch,
|
|
164
|
+
closeIcon: config.closeIcon,
|
|
165
|
+
bannerEventEndpoint: (_a = config.bannerEventEndpoint) !== null && _a !== void 0 ? _a : '/banner-event',
|
|
166
|
+
shopUpdateEndpoint: (_b = config.shopUpdateEndpoint) !== null && _b !== void 0 ? _b : '/shop',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Cookie Bar app icon
|
|
172
|
+
*/
|
|
173
|
+
function CookieBarIcon({ size = 80 }) {
|
|
174
|
+
return (jsxs("svg", { width: size, height: size, viewBox: "0 0 80 80", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("circle", { cx: "40", cy: "40", r: "36", fill: "#FFA726" }), jsx("circle", { cx: "28", cy: "32", r: "6", fill: "#8D6E63" }), jsx("circle", { cx: "48", cy: "28", r: "4", fill: "#8D6E63" }), jsx("circle", { cx: "52", cy: "48", r: "5", fill: "#8D6E63" }), jsx("circle", { cx: "32", cy: "52", r: "4", fill: "#8D6E63" }), jsx("circle", { cx: "44", cy: "44", r: "3", fill: "#8D6E63" })] }));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Age Verification app icon
|
|
178
|
+
*/
|
|
179
|
+
function AgeVerificationIcon({ size = 80 }) {
|
|
180
|
+
return (jsxs("svg", { width: size, height: size, viewBox: "0 0 80 80", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("circle", { cx: "40", cy: "40", r: "36", fill: "#EF5350" }), jsx("text", { x: "40", y: "48", textAnchor: "middle", fill: "white", fontSize: "24", fontWeight: "bold", fontFamily: "Arial, sans-serif", children: "18+" })] }));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Accessibility app icon
|
|
184
|
+
*/
|
|
185
|
+
function AccessibilityIcon({ size = 80 }) {
|
|
186
|
+
return (jsxs("svg", { width: size, height: size, viewBox: "0 0 80 80", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("circle", { cx: "40", cy: "40", r: "36", fill: "#2196F3" }), jsx("circle", { cx: "40", cy: "24", r: "6", fill: "white" }), jsx("path", { d: "M40 32V48M40 48L32 64M40 48L48 64M24 40H56", stroke: "white", strokeWidth: "4", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Order Limit app icon
|
|
190
|
+
*/
|
|
191
|
+
function OrderLimitIcon({ size = 80 }) {
|
|
192
|
+
return (jsxs("svg", { width: size, height: size, viewBox: "0 0 80 80", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("circle", { cx: "40", cy: "40", r: "36", fill: "#9C27B0" }), jsx("path", { d: "M24 32H56L52 52H28L24 32Z", stroke: "white", strokeWidth: "3", fill: "none" }), jsx("circle", { cx: "32", cy: "58", r: "4", fill: "white" }), jsx("circle", { cx: "48", cy: "58", r: "4", fill: "white" })] }));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Default/fallback icon
|
|
196
|
+
*/
|
|
197
|
+
function DefaultIcon({ size = 80 }) {
|
|
198
|
+
return (jsxs("svg", { width: size, height: size, viewBox: "0 0 80 80", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("circle", { cx: "40", cy: "40", r: "36", fill: "#607D8B" }), jsx("text", { x: "40", y: "48", textAnchor: "middle", fill: "white", fontSize: "20", fontWeight: "bold", fontFamily: "Arial, sans-serif", children: "APP" })] }));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* CrossApp icon component that renders the appropriate icon based on app handle
|
|
202
|
+
*/
|
|
203
|
+
function CrossAppIcon({ app, size = 80 }) {
|
|
204
|
+
switch (app) {
|
|
205
|
+
case APP_HANDLES.COOKIE_BAR:
|
|
206
|
+
return jsx(CookieBarIcon, { size: size });
|
|
207
|
+
case APP_HANDLES.AGE_VERIFICATION:
|
|
208
|
+
return jsx(AgeVerificationIcon, { size: size });
|
|
209
|
+
case APP_HANDLES.ACCESSIBILITY:
|
|
210
|
+
return jsx(AccessibilityIcon, { size: size });
|
|
211
|
+
case APP_HANDLES.ORDER_LIMIT:
|
|
212
|
+
return jsx(OrderLimitIcon, { size: size });
|
|
213
|
+
default:
|
|
214
|
+
return jsx(DefaultIcon, { size: size });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function DownloadIcon({ width = 18, height = 20, fill = 'black', }) {
|
|
219
|
+
return (jsxs("svg", { width: width, height: height, viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsx("path", { d: "M10.8333 3.61111C10.8333 3.15087 10.4602 2.77778 9.99999 2.77778C9.53976 2.77778 9.16666 3.15087 9.16666 3.61111V11.0437L7.25592 9.13297C6.93048 8.80753 6.40284 8.80753 6.0774 9.13297C5.75197 9.4584 5.75197 9.98604 6.0774 10.3115L9.41074 13.6448C9.73618 13.9702 10.2638 13.9702 10.5893 13.6448L13.9226 10.3115C14.248 9.98604 14.248 9.4584 13.9226 9.13297C13.5971 8.80753 13.0695 8.80753 12.7441 9.13297L10.8333 11.0437V3.61111Z", fill: fill }), jsx("path", { d: "M17.2222 14.1667C17.2222 13.7064 16.8491 13.3333 16.3889 13.3333C15.9286 13.3333 15.5555 13.7064 15.5555 14.1667V15.0556C15.5555 15.5158 15.1825 15.8889 14.7222 15.8889L5.27777 15.8889C4.81753 15.8889 4.44444 15.5158 4.44444 15.0556L4.44444 14.1667C4.44444 13.7064 4.07134 13.3333 3.6111 13.3333C3.15087 13.3333 2.77777 13.7064 2.77777 14.1667V15.0556C2.77777 16.4363 3.89706 17.5556 5.27777 17.5556L14.7222 17.5556C16.1029 17.5556 17.2222 16.4363 17.2222 15.0556V14.1667Z", fill: fill })] }));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* CrossApp Banner Component
|
|
224
|
+
*
|
|
225
|
+
* A reusable banner component for cross-promoting Avada apps.
|
|
226
|
+
* Supports configuration-based customization for different apps.
|
|
227
|
+
*/
|
|
228
|
+
function CrossAppBanner$1({ config, appData, sourcePage = "dashboard", shop = {}, onClose, onShopUpdate, }) {
|
|
229
|
+
var _a;
|
|
230
|
+
const { appHandle, translationKey, targetUrl, showPlanBtn = false, planUrl, bgColor = "linear-gradient(90deg, #000926 0%, #0a1f3d 100%)", isHideBanner = false, eventPrefix, bannerCloseKey = "bannerCloseCount", } = useMemo(() => appData, [appData]);
|
|
231
|
+
const isVerificationApp = useMemo(() => appHandle === APP_HANDLES.AGE_VERIFICATION, [appHandle]);
|
|
232
|
+
const [showBanner, setShowBanner] = useState(!isHideBanner);
|
|
233
|
+
const getEventName = useCallback((action) => {
|
|
234
|
+
const prefix = eventPrefix !== null && eventPrefix !== void 0 ? eventPrefix : config.appPrefix;
|
|
235
|
+
return `${prefix}_banner_${action}`;
|
|
236
|
+
}, [eventPrefix, config.appPrefix]);
|
|
237
|
+
const logBannerEvent = useCallback(async (action, isClosePermanently = false) => {
|
|
238
|
+
var _a;
|
|
239
|
+
if (!isVerificationApp)
|
|
240
|
+
return;
|
|
241
|
+
const eventName = getEventName(action);
|
|
242
|
+
const endpoint = (_a = config.bannerEventEndpoint) !== null && _a !== void 0 ? _a : "/banner-event";
|
|
243
|
+
try {
|
|
244
|
+
await config.apiClient(endpoint, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: {
|
|
247
|
+
event: eventName,
|
|
248
|
+
properties: {
|
|
249
|
+
[`${config.appPrefix}_banner_channel`]: sourcePage,
|
|
250
|
+
},
|
|
251
|
+
isClosePermanently,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error(`[Tracking Error] ${eventName}:`, error);
|
|
257
|
+
}
|
|
258
|
+
}, [isVerificationApp, getEventName, config, sourcePage]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (showBanner && isVerificationApp) {
|
|
261
|
+
logBannerEvent("display");
|
|
262
|
+
}
|
|
263
|
+
}, [showBanner, isVerificationApp, logBannerEvent]);
|
|
264
|
+
const handleClose = useCallback(() => {
|
|
265
|
+
setShowBanner(false);
|
|
266
|
+
if (isVerificationApp && bannerCloseKey) {
|
|
267
|
+
const currentCount = Number(localStorage.getItem(bannerCloseKey)) || 0;
|
|
268
|
+
const nextCount = currentCount + 1;
|
|
269
|
+
localStorage.setItem(bannerCloseKey, String(nextCount));
|
|
270
|
+
if (nextCount >= 2) {
|
|
271
|
+
// Update shop to hide banner permanently
|
|
272
|
+
if (onShopUpdate) {
|
|
273
|
+
onShopUpdate({ ...shop, hideVerificationBanner: true });
|
|
274
|
+
}
|
|
275
|
+
if (config.storeDispatch) {
|
|
276
|
+
config.storeDispatch({
|
|
277
|
+
type: "SET_SHOP",
|
|
278
|
+
payload: { ...shop, hideVerificationBanner: true },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
logBannerEvent("close", true);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
logBannerEvent("close");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
288
|
+
}, [
|
|
289
|
+
isVerificationApp,
|
|
290
|
+
bannerCloseKey,
|
|
291
|
+
shop,
|
|
292
|
+
onShopUpdate,
|
|
293
|
+
config.storeDispatch,
|
|
294
|
+
logBannerEvent,
|
|
295
|
+
onClose,
|
|
296
|
+
]);
|
|
297
|
+
const handleCtaClick = useCallback(() => {
|
|
298
|
+
if (isVerificationApp) {
|
|
299
|
+
logBannerEvent("access");
|
|
300
|
+
}
|
|
301
|
+
}, [isVerificationApp, logBannerEvent]);
|
|
302
|
+
if (!showBanner || !appData)
|
|
303
|
+
return null;
|
|
304
|
+
const closeIcon = (_a = config.closeIcon) !== null && _a !== void 0 ? _a : jsx(Icon, { source: XIcon });
|
|
305
|
+
return (jsx(Layout.Section, { children: jsx(Box, { borderRadius: "200", overflowX: "hidden", overflowY: "hidden", children: jsxs("div", { className: "Avada-CrossApp", style: { background: bgColor }, children: [jsxs(InlineStack, { gap: isVerificationApp ? "400" : "0", children: [jsx("div", { className: "Avada-CrossApp__Icon", children: jsx(CrossAppIcon, { app: appHandle, size: 80 }) }), jsxs("div", { className: "Avada-CrossApp__Content", children: [jsx("div", { className: "Avada-CrossApp__Content--Text", children: jsxs(BlockStack, { gap: "200", children: [jsxs(Text, { as: "h3", variant: "headingLg", fontWeight: "bold", children: [translationKey, ".title"] }), jsxs(BlockStack, { gap: isVerificationApp ? "200" : "0", children: [isVerificationApp ? (jsx("i", { children: jsxs(Text, { as: "p", variant: "bodyLg", fontWeight: "medium", children: [translationKey, ".description1"] }) })) : (jsxs(Text, { as: "p", variant: "bodyMd", children: [translationKey, ".description1"] })), jsxs(Text, { as: "p", variant: "bodyMd", children: [translationKey, ".description2"] })] })] }) }), jsx("div", { className: "Avada-CrossApp__Content--Btn", children: jsxs(BlockStack, { gap: "200", inlineAlign: "center", children: [jsx(Button, { url: targetUrl, target: "_blank", onClick: handleCtaClick, fullWidth: true, children: jsxs(InlineStack, { blockAlign: "center", gap: "100", children: [jsx(DownloadIcon, {}), jsxs(Text, { as: "span", children: [translationKey, ".btnClaim"] })] }) }), showPlanBtn && planUrl && (jsx("div", { className: "Avada-CrossApp__ViewPlan", children: jsx(Link, { url: planUrl, target: "_blank", children: jsxs("span", { children: [translationKey, ".btnViewPlan"] }) }) }))] }) })] })] }), jsx("button", { className: "Avada-CrossApp__CloseBtn", onClick: handleClose, "aria-label": "Close banner", type: "button", children: closeIcon })] }) }) }));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* CrossApp Banner Component with i18n support
|
|
310
|
+
*
|
|
311
|
+
* A reusable banner component for cross-promoting Avada apps.
|
|
312
|
+
* Uses @shopify/react-i18n for translations.
|
|
313
|
+
*/
|
|
314
|
+
function CrossAppBannerWithI18n({ config, appData, sourcePage = 'dashboard', shop = {}, onClose, onShopUpdate, }) {
|
|
315
|
+
var _a;
|
|
316
|
+
const [i18n] = useI18n();
|
|
317
|
+
const { appHandle, translationKey, targetUrl, showPlanBtn = false, planUrl, bgColor = 'linear-gradient(90deg, #000926 0%, #0a1f3d 100%)', isHideBanner = false, eventPrefix, bannerCloseKey = 'bannerCloseCount', } = useMemo(() => appData, [appData]);
|
|
318
|
+
const isVerificationApp = useMemo(() => appHandle === APP_HANDLES.AGE_VERIFICATION, [appHandle]);
|
|
319
|
+
const [showBanner, setShowBanner] = useState(!isHideBanner);
|
|
320
|
+
const getEventName = useCallback((action) => {
|
|
321
|
+
const prefix = eventPrefix !== null && eventPrefix !== void 0 ? eventPrefix : config.appPrefix;
|
|
322
|
+
return `${prefix}_banner_${action}`;
|
|
323
|
+
}, [eventPrefix, config.appPrefix]);
|
|
324
|
+
const logBannerEvent = useCallback(async (action, isClosePermanently = false) => {
|
|
325
|
+
var _a;
|
|
326
|
+
if (!isVerificationApp)
|
|
327
|
+
return;
|
|
328
|
+
const eventName = getEventName(action);
|
|
329
|
+
const endpoint = (_a = config.bannerEventEndpoint) !== null && _a !== void 0 ? _a : '/banner-event';
|
|
330
|
+
try {
|
|
331
|
+
await config.apiClient(endpoint, {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
body: {
|
|
334
|
+
event: eventName,
|
|
335
|
+
properties: {
|
|
336
|
+
[`${config.appPrefix}_banner_channel`]: sourcePage,
|
|
337
|
+
},
|
|
338
|
+
isClosePermanently,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error(`[Tracking Error] ${eventName}:`, error);
|
|
344
|
+
}
|
|
345
|
+
}, [isVerificationApp, getEventName, config, sourcePage]);
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (showBanner && isVerificationApp) {
|
|
348
|
+
logBannerEvent('display');
|
|
349
|
+
}
|
|
350
|
+
}, [showBanner, isVerificationApp, logBannerEvent]);
|
|
351
|
+
const handleClose = useCallback(() => {
|
|
352
|
+
setShowBanner(false);
|
|
353
|
+
if (isVerificationApp && bannerCloseKey) {
|
|
354
|
+
const currentCount = Number(localStorage.getItem(bannerCloseKey)) || 0;
|
|
355
|
+
const nextCount = currentCount + 1;
|
|
356
|
+
localStorage.setItem(bannerCloseKey, String(nextCount));
|
|
357
|
+
if (nextCount >= 2) {
|
|
358
|
+
if (onShopUpdate) {
|
|
359
|
+
onShopUpdate({ ...shop, hideVerificationBanner: true });
|
|
360
|
+
}
|
|
361
|
+
if (config.storeDispatch) {
|
|
362
|
+
config.storeDispatch({
|
|
363
|
+
type: 'SET_SHOP',
|
|
364
|
+
payload: { ...shop, hideVerificationBanner: true },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
logBannerEvent('close', true);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
logBannerEvent('close');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
374
|
+
}, [
|
|
375
|
+
isVerificationApp,
|
|
376
|
+
bannerCloseKey,
|
|
377
|
+
shop,
|
|
378
|
+
onShopUpdate,
|
|
379
|
+
config.storeDispatch,
|
|
380
|
+
logBannerEvent,
|
|
381
|
+
onClose,
|
|
382
|
+
]);
|
|
383
|
+
const handleCtaClick = useCallback(() => {
|
|
384
|
+
if (isVerificationApp) {
|
|
385
|
+
logBannerEvent('access');
|
|
386
|
+
}
|
|
387
|
+
}, [isVerificationApp, logBannerEvent]);
|
|
388
|
+
if (!showBanner || !appData)
|
|
389
|
+
return null;
|
|
390
|
+
const closeIcon = (_a = config.closeIcon) !== null && _a !== void 0 ? _a : jsx(Icon, { source: XIcon });
|
|
391
|
+
return (jsx(Layout.Section, { children: jsx(Box, { borderRadius: "200", overflowX: "hidden", overflowY: "hidden", children: jsxs("div", { className: "Avada-CrossApp", style: { background: bgColor }, children: [jsxs(InlineStack, { gap: isVerificationApp ? '400' : '0', children: [jsx("div", { className: "Avada-CrossApp__Icon", children: jsx(CrossAppIcon, { app: appHandle, size: 80 }) }), jsxs("div", { className: "Avada-CrossApp__Content", children: [jsx("div", { className: "Avada-CrossApp__Content--Text", children: jsxs(BlockStack, { gap: "200", children: [jsx(Text, { as: "h3", variant: "headingLg", fontWeight: "bold", children: i18n.translate(`${translationKey}.title`) }), jsxs(BlockStack, { gap: isVerificationApp ? '200' : '0', children: [isVerificationApp ? (jsx("i", { children: jsx(Text, { as: "p", variant: "bodyLg", fontWeight: "medium", children: i18n.translate(`${translationKey}.description1`) }) })) : (jsx(Text, { as: "p", variant: "bodyMd", children: i18n.translate(`${translationKey}.description1`) })), jsx(Text, { as: "p", variant: "bodyMd", children: i18n.translate(`${translationKey}.description2`) })] })] }) }), jsx("div", { className: "Avada-CrossApp__Content--Btn", children: jsxs(BlockStack, { gap: "200", inlineAlign: "center", children: [jsx(Button, { url: targetUrl, target: "_blank", onClick: handleCtaClick, fullWidth: true, children: jsxs(InlineStack, { blockAlign: "center", gap: "100", children: [jsx(DownloadIcon, {}), jsx(Text, { as: "span", children: i18n.translate(`${translationKey}.btnClaim`) })] }) }), showPlanBtn && planUrl && (jsx("div", { className: "Avada-CrossApp__ViewPlan", children: jsx(Link, { url: planUrl, target: "_blank", children: jsx("span", { children: i18n.translate(`${translationKey}.btnViewPlan`) }) }) }))] }) })] })] }), jsx("button", { className: "Avada-CrossApp__CloseBtn", onClick: handleClose, "aria-label": "Close banner", type: "button", children: closeIcon })] }) }) }));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
CrossAppBanner;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get data from localStorage
|
|
398
|
+
*/
|
|
399
|
+
function getStorageData(key) {
|
|
400
|
+
try {
|
|
401
|
+
const data = localStorage.getItem(key);
|
|
402
|
+
return data ? JSON.parse(data) : {};
|
|
403
|
+
}
|
|
404
|
+
catch (_a) {
|
|
405
|
+
return {};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Set data to localStorage
|
|
410
|
+
*/
|
|
411
|
+
function setStorageData(key, data) {
|
|
412
|
+
try {
|
|
413
|
+
localStorage.setItem(key, JSON.stringify(data));
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
console.error('Failed to save to localStorage:', e);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Check if object is empty
|
|
421
|
+
*/
|
|
422
|
+
function isEmpty(obj) {
|
|
423
|
+
return Object.keys(obj).length === 0;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Calculate next display date based on target type
|
|
427
|
+
*/
|
|
428
|
+
function getNextDisplayDate(targetDateType) {
|
|
429
|
+
const currentDate = new Date();
|
|
430
|
+
switch (targetDateType) {
|
|
431
|
+
case 'next_day':
|
|
432
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
433
|
+
break;
|
|
434
|
+
case '7_days_after':
|
|
435
|
+
currentDate.setDate(currentDate.getDate() + 7);
|
|
436
|
+
break;
|
|
437
|
+
case '30_days_after':
|
|
438
|
+
currentDate.setDate(currentDate.getDate() + 30);
|
|
439
|
+
break;
|
|
440
|
+
default:
|
|
441
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
442
|
+
}
|
|
443
|
+
return currentDate;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Hook to manage banner display state with localStorage persistence
|
|
447
|
+
*/
|
|
448
|
+
function useDisplayBanner({ storageKey, shopKey = '', shop = {}, checkShop = false, disableCount = 3, targetDateType = 'next_day', apiClient, shopUpdateEndpoint = '/shops?type=banner', onShopUpdate, onClose, checkStorage = true, }) {
|
|
449
|
+
var _a;
|
|
450
|
+
const [isCheckShop, setIsCheckShop] = useState(checkShop);
|
|
451
|
+
const dismissedBanners = (_a = shop === null || shop === void 0 ? void 0 : shop.dismissedBanners) !== null && _a !== void 0 ? _a : [];
|
|
452
|
+
const isDismissed = useMemo(() => shopKey ? dismissedBanners.includes(shopKey) : false, [dismissedBanners, shopKey]);
|
|
453
|
+
const handleShopApi = useCallback(async (data) => {
|
|
454
|
+
if (!apiClient)
|
|
455
|
+
return;
|
|
456
|
+
try {
|
|
457
|
+
const resp = await apiClient(shopUpdateEndpoint, {
|
|
458
|
+
body: data,
|
|
459
|
+
method: 'PUT',
|
|
460
|
+
});
|
|
461
|
+
if (resp.success && onShopUpdate) {
|
|
462
|
+
onShopUpdate({
|
|
463
|
+
...shop,
|
|
464
|
+
...resp.data,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (e) {
|
|
469
|
+
console.error('Failed to update shop:', e);
|
|
470
|
+
}
|
|
471
|
+
}, [apiClient, shopUpdateEndpoint, shop, onShopUpdate]);
|
|
472
|
+
const checkIsShowBanner = useCallback(() => {
|
|
473
|
+
var _a;
|
|
474
|
+
if (!checkStorage) {
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
const storageData = getStorageData(storageKey);
|
|
478
|
+
const isValidStorage = isEmpty(storageData) ||
|
|
479
|
+
new Date((_a = storageData.currentDate) !== null && _a !== void 0 ? _a : '').getTime() < new Date().getTime();
|
|
480
|
+
if (!isCheckShop) {
|
|
481
|
+
return isValidStorage;
|
|
482
|
+
}
|
|
483
|
+
return isValidStorage && !isDismissed;
|
|
484
|
+
}, [storageKey, isCheckShop, isDismissed, checkStorage]);
|
|
485
|
+
const [shouldDisplay, setShouldDisplay] = useState(checkIsShowBanner);
|
|
486
|
+
const hideBanner = useCallback(() => {
|
|
487
|
+
setShouldDisplay(false);
|
|
488
|
+
}, []);
|
|
489
|
+
const showBanner = useCallback(() => {
|
|
490
|
+
setShouldDisplay(true);
|
|
491
|
+
}, []);
|
|
492
|
+
const handleCloseBanner = useCallback(async (data) => {
|
|
493
|
+
var _a;
|
|
494
|
+
const isDismissShop = (data === null || data === void 0 ? void 0 : data.isDismissShop) === true;
|
|
495
|
+
setShouldDisplay(false);
|
|
496
|
+
setIsCheckShop(isDismissShop);
|
|
497
|
+
const storageData = getStorageData(storageKey);
|
|
498
|
+
const count = (_a = storageData.disableCount) !== null && _a !== void 0 ? _a : 1;
|
|
499
|
+
const nextDate = getNextDisplayDate(targetDateType);
|
|
500
|
+
setStorageData(storageKey, {
|
|
501
|
+
currentDate: nextDate.toISOString(),
|
|
502
|
+
disableCount: count + 1,
|
|
503
|
+
});
|
|
504
|
+
if ((isCheckShop && count >= disableCount) || isDismissShop) {
|
|
505
|
+
const newDismissedBanners = [...new Set([...dismissedBanners, shopKey])];
|
|
506
|
+
await handleShopApi({
|
|
507
|
+
dismissedBanners: newDismissedBanners,
|
|
508
|
+
...data,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
512
|
+
}, [
|
|
513
|
+
storageKey,
|
|
514
|
+
targetDateType,
|
|
515
|
+
isCheckShop,
|
|
516
|
+
disableCount,
|
|
517
|
+
dismissedBanners,
|
|
518
|
+
shopKey,
|
|
519
|
+
handleShopApi,
|
|
520
|
+
onClose,
|
|
521
|
+
]);
|
|
522
|
+
return {
|
|
523
|
+
shouldDisplay,
|
|
524
|
+
hideBanner,
|
|
525
|
+
showBanner,
|
|
526
|
+
handleCloseBanner,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export { APP_DOMAINS, APP_HANDLES, CrossAppBanner$1 as CrossAppBanner, CrossAppBannerWithI18n, CrossAppIcon, DownloadIcon, EXCLUDED_COUNTRIES, EXCLUDED_SHOPIFY_PLANS, SHOPIFY_APP_HANDLES, TARGET_STORAGE_TYPES, createCrossAppConfig, CrossAppBanner$1 as default, getAccessibilityAppData, getAgeVerificationAppData, getCookieBarAppData, getShopifyName, shouldExcludeShop, useDisplayBanner };
|
|
531
|
+
//# sourceMappingURL=index.esm.js.map
|