@liquidcommercedev/rmn-sdk 1.5.0-beta.14 → 1.5.0-beta.16

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/index.esm.js CHANGED
@@ -78,574 +78,6 @@ var RMN_ENV;
78
78
  RMN_ENV["PRODUCTION"] = "production";
79
79
  })(RMN_ENV || (RMN_ENV = {}));
80
80
 
81
- const SPOT_EVENTS_EXAMPLE = [
82
- {
83
- event: RMN_SPOT_EVENT.CLICK,
84
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwidXIiOm51bGx9&s=hWz37kbxi_u95EVNn2aoQhc5Aas',
85
- },
86
- {
87
- event: RMN_SPOT_EVENT.IMPRESSION,
88
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiYmEiOjEsImZxIjowfQ&s=djoysjCimurf-5T11AlNAwwLSS8',
89
- },
90
- {
91
- event: RMN_SPOT_EVENT.PURCHASE,
92
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjU5fQ&s=AAPAw-3SfZ0JMzjEGFSwt9L-2S4',
93
- },
94
- {
95
- event: RMN_SPOT_EVENT.ADD_TO_CART,
96
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYwfQ&s=uzQFcjgL7m9XqUG8FvTPVN5YkZY',
97
- },
98
- {
99
- event: RMN_SPOT_EVENT.ADD_TO_WISHLIST,
100
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYzfQ&s=m3ISU_iIy-OFtXrTKpI6cJAEC0k',
101
- },
102
- {
103
- event: RMN_SPOT_EVENT.BUY_NOW,
104
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
105
- },
106
- ];
107
- const RB_SPOTS_SELECTION_EXAMPLE = {
108
- rbHomepageHero: [
109
- {
110
- id: 'abc123',
111
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
112
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
113
- width: 1140,
114
- height: 640,
115
- header: 'Premium Wine Collection',
116
- description: 'Discover our exclusive selection of vintage wines',
117
- ctaText: 'Shop Wines',
118
- textColor: '#ffffff',
119
- ctaTextColor: '#ffffff',
120
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Collection',
121
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Wine',
122
- events: SPOT_EVENTS_EXAMPLE,
123
- productIds: [1, 2, 3],
124
- },
125
- {
126
- id: 'jkl012',
127
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
128
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
129
- width: 1140,
130
- height: 640,
131
- header: 'Whiskey Collection',
132
- description: 'Premium whiskeys from around the world',
133
- ctaText: 'Shop Whiskeys',
134
- textColor: '#ffffff',
135
- backgroundColor: '#2c1810',
136
- ctaTextColor: '#2c1810',
137
- primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey',
138
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
139
- events: SPOT_EVENTS_EXAMPLE,
140
- productIds: [10, 11],
141
- },
142
- {
143
- id: 'stu901',
144
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
145
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
146
- width: 1140,
147
- height: 640,
148
- header: 'Summer Cocktails',
149
- description: 'Essential spirits for summer mixing',
150
- ctaText: 'Mix It Up',
151
- textColor: '#ffffff',
152
- backgroundColor: '#4d3a0a',
153
- ctaTextColor: '#4d3a0a',
154
- primaryImage: 'https://placehold.co/1140x640/png?text=Cocktails',
155
- secondaryImage: 'https://placehold.co/1140x640/png?text=Mixers',
156
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktails',
157
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mixers',
158
- events: SPOT_EVENTS_EXAMPLE,
159
- productIds: [16, 17, 18],
160
- },
161
- {
162
- id: 'def456',
163
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
164
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
165
- width: 1140,
166
- height: 640,
167
- header: 'Craft Beer Festival',
168
- description: 'Local breweries and exclusive releases',
169
- ctaText: 'Explore Beers',
170
- textColor: '#ffffff',
171
- ctaTextColor: '#ffffff',
172
- primaryImage: 'https://placehold.co/1140x640/png?text=Beer+Festival',
173
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Beer',
174
- events: SPOT_EVENTS_EXAMPLE,
175
- productIds: [4, 5, 6],
176
- },
177
- {
178
- id: 'mno345',
179
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
180
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
181
- width: 1140,
182
- height: 640,
183
- header: 'Champagne Selection',
184
- description: 'Finest champagnes for every occasion',
185
- ctaText: 'View Champagnes',
186
- textColor: '#ffffff',
187
- backgroundColor: '#1a1a1a',
188
- ctaTextColor: '#1a1a1a',
189
- primaryImage: 'https://placehold.co/1140x640/png?text=Champagne',
190
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Champagne',
191
- events: SPOT_EVENTS_EXAMPLE,
192
- productIds: [12, 13],
193
- },
194
- {
195
- id: 'vwx234',
196
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
197
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
198
- width: 1140,
199
- height: 640,
200
- header: 'Wine Regions',
201
- description: 'Explore wines from top regions',
202
- ctaText: 'Tour Regions',
203
- textColor: '#ffffff',
204
- backgroundColor: '#4d0a0a',
205
- ctaTextColor: '#4d0a0a',
206
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Regions',
207
- secondaryImage: 'https://placehold.co/1140x640/png?text=Vineyards',
208
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Regions',
209
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Vineyards',
210
- events: SPOT_EVENTS_EXAMPLE,
211
- productIds: [19, 20, 21],
212
- },
213
- {
214
- id: 'ghi789',
215
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
216
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
217
- width: 1140,
218
- height: 640,
219
- header: 'Rare Spirits',
220
- description: 'Limited edition spirits collection',
221
- ctaText: 'View Collection',
222
- textColor: '#ffffff',
223
- ctaTextColor: '#ffffff',
224
- primaryImage: 'https://placehold.co/1140x640/png?text=Rare+Spirits',
225
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Spirits',
226
- events: SPOT_EVENTS_EXAMPLE,
227
- productIds: [7, 8, 9],
228
- },
229
- {
230
- id: 'pqr678',
231
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
232
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
233
- width: 1140,
234
- height: 640,
235
- header: 'Gin Collection',
236
- description: 'Artisanal gins and botanicals',
237
- ctaText: 'Explore Gins',
238
- textColor: '#ffffff',
239
- backgroundColor: '#0a4d4d',
240
- ctaTextColor: '#0a4d4d',
241
- primaryImage: 'https://placehold.co/1140x640/png?text=Gin',
242
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gin',
243
- events: SPOT_EVENTS_EXAMPLE,
244
- productIds: [14, 15],
245
- },
246
- {
247
- id: 'yz5678',
248
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
249
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
250
- width: 1140,
251
- height: 640,
252
- header: 'Tequila Collection',
253
- description: 'Premium tequilas and mezcals',
254
- ctaText: 'Shop Tequila',
255
- textColor: '#ffffff',
256
- backgroundColor: '#0a4d2b',
257
- ctaTextColor: '#0a4d2b',
258
- primaryImage: 'https://placehold.co/1140x640/png?text=Tequila',
259
- secondaryImage: 'https://placehold.co/1140x640/png?text=Mezcal',
260
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Tequila',
261
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mezcal',
262
- events: SPOT_EVENTS_EXAMPLE,
263
- productIds: [22, 23, 24],
264
- },
265
- ],
266
- rbHomepageHeroFullImage: [
267
- {
268
- id: 'hjk567',
269
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
270
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
271
- width: 1140,
272
- height: 640,
273
- header: 'Holiday Gift Guide',
274
- description: 'Perfect spirits for every occasion',
275
- ctaText: 'Shop Gifts',
276
- textColor: '#ffffff',
277
- ctaTextColor: '#ffffff',
278
- primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
279
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
280
- events: SPOT_EVENTS_EXAMPLE,
281
- productIds: [25, 26],
282
- },
283
- {
284
- id: 'qwe890',
285
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
286
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
287
- width: 1140,
288
- height: 640,
289
- header: 'Summer Wine Festival',
290
- description: 'Refreshing wines for summer',
291
- ctaText: 'Shop Festival',
292
- textColor: '#ffffff',
293
- ctaTextColor: '#ffffff',
294
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
295
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
296
- events: SPOT_EVENTS_EXAMPLE,
297
- productIds: [27, 28],
298
- },
299
- ],
300
- rbHomepageHeroTwoTile: [
301
- {
302
- id: 'iop987',
303
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
304
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
305
- width: 1140,
306
- height: 640,
307
- header: 'Bourbon Selection',
308
- description: "Kentucky's finest bourbons",
309
- ctaText: 'Shop Bourbon',
310
- textColor: '#ffffff',
311
- backgroundColor: '#2c1810',
312
- ctaTextColor: '#2c1810',
313
- primaryImage: 'https://placehold.co/1140x640/png?text=Bourbon',
314
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Bourbon',
315
- events: SPOT_EVENTS_EXAMPLE,
316
- productIds: [29, 30],
317
- },
318
- {
319
- id: 'lkj012',
320
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
321
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
322
- width: 1140,
323
- height: 640,
324
- header: 'Vodka Collection',
325
- description: 'Premium vodkas from around the world',
326
- ctaText: 'Shop Vodka',
327
- textColor: '#ffffff',
328
- backgroundColor: '#1a1a1a',
329
- ctaTextColor: '#1a1a1a',
330
- primaryImage: 'https://placehold.co/1140x640/png?text=Vodka',
331
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Vodka',
332
- events: SPOT_EVENTS_EXAMPLE,
333
- productIds: [31, 32],
334
- },
335
- ],
336
- rbHomepageHeroThreeTile: [
337
- {
338
- id: 'bnm345',
339
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
340
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
341
- width: 1140,
342
- height: 640,
343
- header: 'Rum Collection',
344
- description: 'Caribbean and craft rums',
345
- ctaText: 'Shop Rum',
346
- textColor: '#ffffff',
347
- backgroundColor: '#4d3a0a',
348
- ctaTextColor: '#4d3a0a',
349
- primaryImage: 'https://placehold.co/1140x640/png?text=Rum',
350
- secondaryImage: 'https://placehold.co/1140x640/png?text=Craft+Rum',
351
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Rum',
352
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Craft',
353
- events: SPOT_EVENTS_EXAMPLE,
354
- productIds: [33, 34],
355
- },
356
- {
357
- id: 'fgh678',
358
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
359
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
360
- width: 1140,
361
- height: 640,
362
- header: 'Scotch Selection',
363
- description: 'Single malts and blends',
364
- ctaText: 'Shop Scotch',
365
- textColor: '#ffffff',
366
- backgroundColor: '#4d0a0a',
367
- ctaTextColor: '#4d0a0a',
368
- primaryImage: 'https://placehold.co/1140x640/png?text=Scotch',
369
- secondaryImage: 'https://placehold.co/1140x640/png?text=Single+Malts',
370
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Scotch',
371
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Malts',
372
- events: SPOT_EVENTS_EXAMPLE,
373
- productIds: [35, 36],
374
- },
375
- ],
376
- rbLargeCategoryImageTout: [
377
- {
378
- id: 'cde567',
379
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
380
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
381
- width: 468,
382
- height: 410,
383
- header: 'Rare & Limited Edition',
384
- description: 'Discover our collection of hard-to-find and limited release spirits.',
385
- textColor: '#ffffff',
386
- ctaTextColor: '#ffffff',
387
- primaryImage: 'https://placehold.co/468x410/png?text=Rare+Spirits',
388
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
389
- ctaText: 'Shop Rare Spirits',
390
- events: SPOT_EVENTS_EXAMPLE,
391
- productIds: [37, 38, 39, 40, 41],
392
- },
393
- {
394
- id: 'efg789',
395
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
396
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
397
- width: 468,
398
- height: 410,
399
- header: 'Vintage Champagnes',
400
- description: 'Explore our prestigious collection of aged champagnes.',
401
- textColor: '#ffffff',
402
- ctaTextColor: '#ffffff',
403
- primaryImage: 'https://placehold.co/468x410/png?text=Vintage+Champagne',
404
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Champagne',
405
- ctaText: 'View Collection',
406
- events: SPOT_EVENTS_EXAMPLE,
407
- productIds: [42, 43, 44, 45, 46],
408
- },
409
- {
410
- id: 'ghi012',
411
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
412
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
413
- width: 468,
414
- height: 410,
415
- header: 'Small Batch Bourbon',
416
- description: 'Hand-selected premium bourbon from craft distilleries.',
417
- textColor: '#ffffff',
418
- ctaTextColor: '#ffffff',
419
- primaryImage: 'https://placehold.co/468x410/png?text=Craft+Bourbon',
420
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Bourbon',
421
- ctaText: 'Explore Bourbon',
422
- events: SPOT_EVENTS_EXAMPLE,
423
- productIds: [47, 48, 49, 50, 51],
424
- },
425
- ],
426
- rbSmallDiscoverTout: [
427
- {
428
- id: 'jkl345',
429
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
430
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
431
- width: 224,
432
- height: 378,
433
- header: 'Château Margaux 2015 Bordeaux',
434
- textColor: '#ffffff',
435
- primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
436
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
437
- events: SPOT_EVENTS_EXAMPLE,
438
- productIds: [52, 53, 54, 55, 56],
439
- },
440
- {
441
- id: 'lmn678',
442
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
443
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
444
- width: 224,
445
- height: 378,
446
- header: 'Macallan 25 Year',
447
- textColor: '#ffffff',
448
- primaryImage: 'https://placehold.co/224x378/png?text=Macallan+25',
449
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Macallan',
450
- events: SPOT_EVENTS_EXAMPLE,
451
- productIds: [57, 58, 59, 60, 61],
452
- },
453
- {
454
- id: 'nop901',
455
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
456
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
457
- width: 224,
458
- height: 378,
459
- header: 'Louis XIII Cognac',
460
- textColor: '#ffffff',
461
- primaryImage: 'https://placehold.co/224x378/png?text=Louis+XIII',
462
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Louis+XIII',
463
- events: SPOT_EVENTS_EXAMPLE,
464
- productIds: [62, 63, 64, 65, 66],
465
- },
466
- ],
467
- rbSmallCategoryImageTout: [
468
- {
469
- id: 'qrs234',
470
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
471
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
472
- width: 224,
473
- height: 410,
474
- header: 'Japanese Sake',
475
- textColor: '#ffffff',
476
- primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
477
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
478
- events: SPOT_EVENTS_EXAMPLE,
479
- productIds: [67, 68, 69, 70, 71],
480
- },
481
- {
482
- id: 'stu567',
483
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
484
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
485
- width: 224,
486
- height: 410,
487
- header: 'Craft Vermouth',
488
- textColor: '#ffffff',
489
- primaryImage: 'https://placehold.co/224x410/png?text=Craft+Vermouth',
490
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Vermouth',
491
- events: SPOT_EVENTS_EXAMPLE,
492
- productIds: [72, 73, 74, 75, 76],
493
- },
494
- {
495
- id: 'vwx890',
496
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
497
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
498
- width: 224,
499
- height: 410,
500
- header: 'Premium Mezcal',
501
- textColor: '#ffffff',
502
- primaryImage: 'https://placehold.co/224x410/png?text=Premium+Mezcal',
503
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Mezcal',
504
- events: SPOT_EVENTS_EXAMPLE,
505
- productIds: [77, 78, 79, 80, 81],
506
- },
507
- ],
508
- rbCollectionBannerWithoutTextBlock: [
509
- {
510
- id: 'yz1234',
511
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
512
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
513
- width: 887,
514
- height: 344,
515
- primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
516
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
517
- events: SPOT_EVENTS_EXAMPLE,
518
- productIds: [82, 83, 84, 85, 86],
519
- },
520
- {
521
- id: 'abc567',
522
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
523
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
524
- width: 887,
525
- height: 344,
526
- primaryImage: 'https://placehold.co/887x344/png?text=Holiday+Spirits',
527
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Holiday+Spirits',
528
- events: SPOT_EVENTS_EXAMPLE,
529
- productIds: [87, 88, 89, 90, 91],
530
- },
531
- {
532
- id: 'def901',
533
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
534
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
535
- width: 887,
536
- height: 344,
537
- primaryImage: 'https://placehold.co/887x344/png?text=Wine+Essentials',
538
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Wine+Essentials',
539
- events: SPOT_EVENTS_EXAMPLE,
540
- productIds: [92, 93, 94, 95, 96],
541
- },
542
- ],
543
- rbNavigationBanner: [
544
- {
545
- id: 'ghi234',
546
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
547
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
548
- width: 440,
549
- height: 220,
550
- header: 'Explore Tequilas',
551
- textColor: '#ffffff',
552
- primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
553
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
554
- events: SPOT_EVENTS_EXAMPLE,
555
- productIds: [97, 98, 99, 100, 101],
556
- },
557
- {
558
- id: 'jkl678',
559
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
560
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
561
- width: 440,
562
- height: 220,
563
- header: 'Craft Gin Selection',
564
- textColor: '#ffffff',
565
- primaryImage: 'https://placehold.co/440x220/png?text=Gin+Selection',
566
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Gin+Selection',
567
- events: SPOT_EVENTS_EXAMPLE,
568
- productIds: [102, 103, 104, 105, 106],
569
- },
570
- {
571
- id: 'mno012',
572
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
573
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
574
- width: 440,
575
- height: 220,
576
- header: 'Premium Vodka',
577
- textColor: '#ffffff',
578
- primaryImage: 'https://placehold.co/440x220/png?text=Vodka+Premium',
579
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Vodka+Premium',
580
- events: SPOT_EVENTS_EXAMPLE,
581
- productIds: [107, 108, 109, 110, 111],
582
- },
583
- ],
584
- };
585
- const IAB_SPOTS_SELECTION_EXAMPLE = {
586
- banner: [],
587
- billboard: [
588
- {
589
- id: 'kol567',
590
- spot: RMN_SPOT_TYPE.BILLBOARD,
591
- variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
592
- width: 1140,
593
- height: 640,
594
- header: 'Holiday Gift Guide',
595
- description: 'Perfect spirits for every occasion',
596
- ctaText: 'Shop Gifts',
597
- textColor: '#ffffff',
598
- ctaTextColor: '#ffffff',
599
- primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
600
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
601
- events: SPOT_EVENTS_EXAMPLE,
602
- productIds: [25, 26],
603
- },
604
- {
605
- id: 'hpm390',
606
- spot: RMN_SPOT_TYPE.BILLBOARD,
607
- variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
608
- width: 1140,
609
- height: 640,
610
- header: 'Summer Wine Festival',
611
- description: 'Refreshing wines for summer',
612
- ctaText: 'Shop Festival',
613
- textColor: '#ffffff',
614
- ctaTextColor: '#ffffff',
615
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
616
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
617
- events: SPOT_EVENTS_EXAMPLE,
618
- productIds: [27, 28],
619
- },
620
- ],
621
- button2: [],
622
- featurePhoneLargeBanner: [],
623
- featurePhoneMediumBanner: [],
624
- featurePhoneSmallBanner: [],
625
- halfPage: [],
626
- inText: [],
627
- largeLeaderboard: [],
628
- largeRectangle: [],
629
- leaderboard: [],
630
- mediumRectangle: [],
631
- microBar: [],
632
- mobilePhoneInterstitial1: [],
633
- mobilePhoneInterstitial2: [],
634
- mobilePhoneInterstitial3: [],
635
- popUp: [],
636
- portrait: [],
637
- rbProductUpcs: [],
638
- skyscraper: [],
639
- smallRectangle: [],
640
- smallSquare: [],
641
- smartphoneBanner1: [],
642
- smartphoneBanner2: [],
643
- square: [],
644
- verticalBanner: [],
645
- verticalRectangle: [],
646
- wideSkyscraper: [],
647
- };
648
-
649
81
  const REQUEST_CLOUD_PARTNER_SITE = 'X-Liquid-Partner-Site';
650
82
  const REQUEST_CLOUD_PROTECTED_KEY = 'X-Liquid-Protected';
651
83
  const REQUEST_CLOUD_PROTECTED_TIMESTAMP = 'X-Liquid-Timestamp';
@@ -16260,6 +15692,7 @@ class LocalStorageService {
16260
15692
  console.warn('Local storage is not supported in this environment');
16261
15693
  return;
16262
15694
  }
15695
+ this.setUserId();
16263
15696
  this.spots = new Map();
16264
15697
  // Sync local storage with the current state
16265
15698
  this.syncLocalStorage();
@@ -16346,6 +15779,45 @@ class LocalStorageService {
16346
15779
  });
16347
15780
  this.updateLocalStorage();
16348
15781
  }
15782
+ // ======================== Utility functions ======================== //
15783
+ getUserId() {
15784
+ return LocalStorageService.localStorageKey.replace(`${LocalStorageService.localStorageKeyPrefix}_`, '');
15785
+ }
15786
+ /**
15787
+ * Sets the user ID in the local storage.
15788
+ * If no existing key is found,
15789
+ * it generates a new unique ID and sets it as the local storage key.
15790
+ */
15791
+ setUserId() {
15792
+ const prefix = LocalStorageService.localStorageKeyPrefix;
15793
+ const existingKeys = Object.keys(window.localStorage).filter((key) => key.startsWith(prefix));
15794
+ const setNewKey = () => {
15795
+ const uniqueId = UniqueIdGenerator.generate();
15796
+ const newLocalStorageKey = `${prefix}_${uniqueId}`;
15797
+ window.localStorage.setItem(newLocalStorageKey, '');
15798
+ LocalStorageService.localStorageKey = newLocalStorageKey;
15799
+ };
15800
+ if (existingKeys.length === 0) {
15801
+ setNewKey();
15802
+ }
15803
+ else {
15804
+ const validKey = existingKeys.find((key) => UniqueIdGenerator.isValid(key.replace(`${prefix}_`, '')));
15805
+ // Delete all other keys except the valid one
15806
+ existingKeys.forEach((key) => {
15807
+ if (key !== validKey) {
15808
+ window.localStorage.removeItem(key);
15809
+ }
15810
+ });
15811
+ if (validKey) {
15812
+ // Found a valid key, assign it to localStorageKey
15813
+ LocalStorageService.localStorageKey = validKey;
15814
+ }
15815
+ else {
15816
+ // No valid key found, generate a new key
15817
+ setNewKey();
15818
+ }
15819
+ }
15820
+ }
16349
15821
  mapToObject(map) {
16350
15822
  return Object.fromEntries(map);
16351
15823
  }
@@ -16399,7 +15871,8 @@ class LocalStorageService {
16399
15871
  return atob(data);
16400
15872
  }
16401
15873
  }
16402
- LocalStorageService.localStorageKey = 'lc_rmn';
15874
+ LocalStorageService.localStorageKeyPrefix = 'lc_rmn';
15875
+ LocalStorageService.localStorageKey = '';
16403
15876
  LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
16404
15877
  LocalStorageService.encryptData = true;
16405
15878
 
@@ -18838,651 +18311,1319 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
18838
18311
  font-size: 28px;
18839
18312
  }
18840
18313
 
18841
- .${prefix}__description {
18842
- font-size: 14px;
18843
- }
18314
+ .${prefix}__description {
18315
+ font-size: 14px;
18316
+ }
18317
+
18318
+ .${prefix}__cta-button {
18319
+ font-size: 13px;
18320
+ }
18321
+ }
18322
+
18323
+ @container (min-width: 1280px) {
18324
+ .${prefix}__cta-button {
18325
+ font-size: 14px;
18326
+ }
18327
+ }
18328
+ </style>
18329
+ `;
18330
+ };
18331
+ function rbLargeCategoryImageToutTemplate(spot, config) {
18332
+ const { prefix = '' } = config;
18333
+ return `
18334
+ ${GFONT_PRECONNECT}
18335
+ ${GFONT_SOURCE_SANS_3}
18336
+ ${GFONT_CORMORANT}
18337
+ ${STYLES$3(spot, config)}
18338
+ <div class="${prefix}">
18339
+ <div class="${prefix}__text">
18340
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18341
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
18342
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
18343
+ </div>
18344
+ </div>
18345
+ `;
18346
+ }
18347
+
18348
+ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18349
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18350
+ return `
18351
+ <style>
18352
+ .${prefix} {
18353
+ width: 100%;
18354
+ height: 100%;
18355
+ display: flex;
18356
+ flex-direction: column;
18357
+ justify-content: flex-end;
18358
+ border-radius: 5px;
18359
+ overflow: hidden;
18360
+ cursor: pointer;
18361
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18362
+ background-size: cover;
18363
+ background-position: center;
18364
+ background-repeat: no-repeat;
18365
+ position: relative;
18366
+ }
18367
+
18368
+ .${prefix}__header {
18369
+ font-size: 16px;
18370
+ color: ${textColor};
18371
+ line-height: 20px;
18372
+ font-weight: 400;
18373
+ font-family: "Source Sans 3", system-ui;
18374
+ font-style: normal;
18375
+ margin: 0;
18376
+ padding: 15px 10%;
18377
+ }
18378
+
18379
+ @container (min-width: 640px) {
18380
+ .${prefix} {
18381
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18382
+ }
18383
+ }
18384
+
18385
+ @container (min-width: 768px) {
18386
+ .${prefix}__header {
18387
+ font-size: 22px;
18388
+ }
18389
+ }
18390
+
18391
+ @container (min-width: 1024px) {
18392
+ .${prefix}__header {
18393
+ font-size: 24px;
18394
+ }
18395
+ }
18396
+
18397
+ @container (min-width: 1280px) {
18398
+ .${prefix}__header {
18399
+ font-size: 28px;
18400
+ }
18401
+ }
18402
+ </style>
18403
+ `;
18404
+ };
18405
+ function rbNavigationBannerTemplate(spot, config) {
18406
+ const { prefix = '' } = config;
18407
+ return `
18408
+ ${GFONT_PRECONNECT}
18409
+ ${GFONT_SOURCE_SANS_3}
18410
+ ${STYLES$2(spot, config)}
18411
+ <div class="${prefix}">
18412
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18413
+ </div>
18414
+ `;
18415
+ }
18416
+
18417
+ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18418
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18419
+ return `
18420
+ <style>
18421
+ .${prefix} {
18422
+ width: 100%;
18423
+ height: 100%;
18424
+ display: flex;
18425
+ flex-direction: column;
18426
+ justify-content: flex-end;
18427
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18428
+ background-size: cover;
18429
+ background-repeat: no-repeat;
18430
+ background-position: center;
18431
+ border-radius: 5px;
18432
+ overflow: hidden;
18433
+ cursor: pointer;
18434
+ position: relative;
18435
+ }
18436
+
18437
+ .${prefix}__header {
18438
+ font-size: 12px;
18439
+ color: ${textColor};
18440
+ line-height: 16px;
18441
+ font-family: "Source Sans 3", system-ui;
18442
+ font-style: normal;
18443
+ font-weight: 400;
18444
+ margin: 0;
18445
+ padding: 10px;
18446
+ }
18447
+
18448
+ @container (min-width: 640px) {
18449
+ .${prefix} {
18450
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18451
+ }
18452
+ }
18453
+ </style>
18454
+ `;
18455
+ };
18456
+ function rbSmallCategoryImageToutTemplate(spot, config) {
18457
+ const { prefix = '' } = config;
18458
+ return `
18459
+ ${GFONT_PRECONNECT}
18460
+ ${GFONT_CORMORANT}
18461
+ ${STYLES$1(spot, config)}
18462
+ <div class="${prefix}">
18463
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18464
+ </div>
18465
+ `;
18466
+ }
18467
+
18468
+ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
18469
+ <style>
18470
+ .${prefix} {
18471
+ width: 100%;
18472
+ height: 100%;
18473
+ background-color: ${backgroundColor};
18474
+ cursor: pointer;
18475
+ display: flex;
18476
+ flex-direction: column;
18477
+ border-radius: 5px;
18478
+ }
18479
+
18480
+ .${prefix}__image {
18481
+ width: 100%;
18482
+ height: 100%;
18483
+ background-image: url("${mobilePrimaryImage}");
18484
+ background-size: cover;
18485
+ background-repeat: no-repeat;
18486
+ background-position: center;
18487
+ border-radius: 5px;
18488
+ }
18489
+
18490
+ .${prefix}__text {
18491
+ text-align: left;
18492
+ display: flex;
18493
+ flex-direction: row;
18494
+ align-items: flex-start;
18495
+ justify-content: flex-start;
18496
+ width: 100%;
18497
+ height: fit-content;
18498
+ position: relative;
18499
+ }
18844
18500
 
18845
- .${prefix}__cta-button {
18846
- font-size: 13px;
18847
- }
18848
- }
18501
+ .${prefix}__header {
18502
+ font-size: 12px;
18503
+ color: ${textColor};
18504
+ padding-top: 5px;
18505
+ line-height: 16px;
18506
+ font-family: "Source Sans 3", system-ui;
18507
+ font-style: normal;
18508
+ font-weight: 400;
18509
+ margin: 0;
18510
+ }
18849
18511
 
18850
- @container (min-width: 1280px) {
18851
- .${prefix}__cta-button {
18852
- font-size: 14px;
18853
- }
18512
+ @container (min-width: 640px) {
18513
+ .${prefix}__image {
18514
+ background-image: url("${primaryImage}");
18854
18515
  }
18855
- </style>
18856
- `;
18857
- };
18858
- function rbLargeCategoryImageToutTemplate(spot, config) {
18516
+ }
18517
+ </style>
18518
+ `;
18519
+ function rbSmallDiscoverToutTemplate(spot, config) {
18859
18520
  const { prefix = '' } = config;
18860
18521
  return `
18861
18522
  ${GFONT_PRECONNECT}
18862
- ${GFONT_SOURCE_SANS_3}
18863
18523
  ${GFONT_CORMORANT}
18864
- ${STYLES$3(spot, config)}
18524
+ ${STYLES(spot, config)}
18865
18525
  <div class="${prefix}">
18526
+ <div class="${prefix}__image"></div>
18866
18527
  <div class="${prefix}__text">
18867
18528
  ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18868
- ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
18869
- ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
18870
18529
  </div>
18871
18530
  </div>
18872
18531
  `;
18873
18532
  }
18874
18533
 
18875
- const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18876
- const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18877
- return `
18878
- <style>
18879
- .${prefix} {
18880
- width: 100%;
18881
- height: 100%;
18882
- display: flex;
18883
- flex-direction: column;
18884
- justify-content: flex-end;
18885
- border-radius: 5px;
18886
- overflow: hidden;
18887
- cursor: pointer;
18888
- background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18889
- background-size: cover;
18890
- background-position: center;
18891
- background-repeat: no-repeat;
18892
- position: relative;
18534
+ /**
18535
+ * Returns the HTML element for the given spot.
18536
+ *
18537
+ * @param {ISpot} spot - The spot object.
18538
+ * @param {ISpotTemplateConfig} config - The spot template configuration.
18539
+ *
18540
+ * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
18541
+ */
18542
+ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
18543
+ const templates = {
18544
+ // Reserve Bar Spot Templates
18545
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: {
18546
+ rbHomepageHeroThreeTile: rbHomepageHeroThreeTileTemplate,
18547
+ },
18548
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE]: {
18549
+ rbHomepageHeroTwoTile: rbHomepageHeroTwoTileTemplate,
18550
+ },
18551
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE]: {
18552
+ rbHomepageHeroFullImage: rbHomepageHeroFullImageTemplate,
18553
+ },
18554
+ [RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT]: {
18555
+ rbLargeCategoryImageTout: rbLargeCategoryImageToutTemplate,
18556
+ },
18557
+ [RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT]: {
18558
+ rbSmallDiscoverTout: rbSmallDiscoverToutTemplate,
18559
+ },
18560
+ [RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT]: {
18561
+ rbSmallCategoryImageTout: rbSmallCategoryImageToutTemplate,
18562
+ },
18563
+ [RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK]: {
18564
+ rbCollectionBannerWithoutTextBlock: rbCollectionBannerWithoutTextBlockTemplate,
18565
+ },
18566
+ [RMN_SPOT_TYPE.RB_NAVIGATION_BANNER]: {
18567
+ rbNavigationBanner: rbNavigationBannerTemplate,
18568
+ },
18569
+ [RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: {
18570
+ rbProductUpcs: () => '', // No template for this spot type, it will be handled by ReserveBar App.
18571
+ },
18572
+ // IAB Standard Spot Templates
18573
+ [RMN_SPOT_TYPE.BILLBOARD]: {
18574
+ billboardV1: billboardV1Template,
18575
+ billboardV2: billboardV2Template,
18576
+ billboardV3: billboardV3Template,
18577
+ },
18578
+ [RMN_SPOT_TYPE.LARGE_RECTANGLE]: {
18579
+ largeRectangleV1: largeRectangleV1Template,
18580
+ },
18581
+ [RMN_SPOT_TYPE.VERTICAL_RECTANGLE]: {
18582
+ verticalRectangleV1: verticalRectangleV1Template,
18583
+ },
18584
+ [RMN_SPOT_TYPE.SQUARE]: {
18585
+ squareV1: squareV1Template,
18586
+ squareV2: squareV2Template,
18587
+ },
18588
+ [RMN_SPOT_TYPE.LARGE_LEADERBOARD]: {
18589
+ largeLeaderboardV1: largeLeaderboardV1Template,
18590
+ largeLeaderboardV2: largeLeaderboardV2Template,
18591
+ },
18592
+ [RMN_SPOT_TYPE.WIDE_SKYSCRAPER]: {
18593
+ wideSkyscraperV1: wideSkyscraperV1Template,
18594
+ },
18595
+ [RMN_SPOT_TYPE.IN_TEXT]: {
18596
+ inTextV1: inTextV1Template,
18597
+ },
18598
+ };
18599
+ const spotVariants = templates[spot.spot];
18600
+ if (!spotVariants) {
18601
+ return null;
18602
+ }
18603
+ const variantTemplate = spotVariants[spot.variant];
18604
+ if (!variantTemplate) {
18605
+ return null;
18606
+ }
18607
+ // Generate a highly unique prefix to avoid conflicts with other elements.
18608
+ const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
18609
+ const spotHtmlString = variantTemplate(spot, { ...config, prefix });
18610
+ return spotHtmlStringToElement(spotHtmlString);
18611
+ };
18612
+
18613
+ // For the moment, we will only focus on sites that use Google Analytics,
18614
+ // but we will add support for other analytics tools in the future.
18615
+ var AnalyticsTool;
18616
+ (function (AnalyticsTool) {
18617
+ AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18618
+ AnalyticsTool["Other"] = "Other";
18619
+ })(AnalyticsTool || (AnalyticsTool = {}));
18620
+
18621
+ class DataLayerMonitor {
18622
+ constructor() {
18623
+ if (!window.dataLayer) {
18624
+ return;
18625
+ }
18626
+ this.originalPush = window.dataLayer.push;
18627
+ }
18628
+ static getInstance() {
18629
+ if (!DataLayerMonitor.instance) {
18630
+ DataLayerMonitor.instance = new DataLayerMonitor();
18631
+ }
18632
+ return DataLayerMonitor.instance;
18633
+ }
18634
+ setListener(listener) {
18635
+ this.listener = listener;
18636
+ }
18637
+ start() {
18638
+ window.dataLayer.push = (...args) => {
18639
+ const result = this.originalPush.apply(window.dataLayer, args);
18640
+ const pushedEvent = args[0];
18641
+ if (this.listener) {
18642
+ const normalizedData = this.cleanEventData(pushedEvent);
18643
+ if (normalizedData) {
18644
+ this.listener(normalizedData);
18645
+ }
18646
+ }
18647
+ return result;
18648
+ };
18649
+ }
18650
+ cleanEventData(data) {
18651
+ const eventName = getEventTypeFromRawEvent(data.event);
18652
+ if (!eventName) {
18653
+ return null;
18654
+ }
18655
+ const productIds = extractDeepIds(data);
18656
+ return {
18657
+ event: eventName,
18658
+ productIds,
18659
+ };
18660
+ }
18661
+ stop() {
18662
+ if (this.originalPush) {
18663
+ window.dataLayer.push = this.originalPush;
18664
+ }
18665
+ this.listener = undefined;
18666
+ }
18667
+ }
18668
+
18669
+ // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
18670
+ // window.rmnDataLayer = window.rmnDataLayer || [];
18671
+ class MonitorService {
18672
+ constructor() {
18673
+ const analyticsTool = this.detectAnalyticsTool();
18674
+ switch (analyticsTool) {
18675
+ case AnalyticsTool.GoogleAnalytics:
18676
+ this.implementedMonitor = DataLayerMonitor.getInstance();
18677
+ break;
18678
+ case AnalyticsTool.Other:
18679
+ default:
18680
+ console.warn('This site uses an unsupported analytics tool.');
18681
+ break;
18682
+ }
18683
+ if (analyticsTool === AnalyticsTool.Other) {
18684
+ return;
18685
+ }
18686
+ this.pubSubService = PubsubService.getInstance();
18687
+ this.localStorageService = LocalStorageService.getInstance();
18688
+ }
18689
+ static getInstance() {
18690
+ if (!MonitorService.instance) {
18691
+ MonitorService.instance = new MonitorService();
18692
+ }
18693
+ return MonitorService.instance;
18694
+ }
18695
+ start() {
18696
+ if (!this.implementedMonitor)
18697
+ return;
18698
+ this.implementedMonitor.setListener(async (eventData) => {
18699
+ var _a;
18700
+ await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
18701
+ });
18702
+ this.implementedMonitor.start();
18703
+ }
18704
+ async matchAndFireEvent(eventData, spots) {
18705
+ var _a, _b;
18706
+ if (!spots)
18707
+ return;
18708
+ const eventProductIds = new Set(eventData.productIds.map((productId) => String(productId)));
18709
+ for (const spot of Object.values(spots)) {
18710
+ if (!spot.productIds.length)
18711
+ continue;
18712
+ const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(String(productId)));
18713
+ if (hasCommonProductIds) {
18714
+ if (Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
18715
+ await this.fireAndPublishSpotEvent({
18716
+ spotEvent: eventData.event,
18717
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18718
+ placementId: spot.placementId,
18719
+ spotId: spot.spotId,
18720
+ });
18721
+ }
18722
+ }
18723
+ }
18724
+ }
18725
+ async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
18726
+ await fireEvent({
18727
+ event: spotEvent,
18728
+ eventUrl,
18729
+ });
18730
+ if (!this.pubSubService)
18731
+ return;
18732
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18733
+ eventType: spotEvent,
18734
+ placementId,
18735
+ spotId,
18736
+ });
18737
+ }
18738
+ detectAnalyticsTool() {
18739
+ let analyticsTool = AnalyticsTool.Other;
18740
+ // Check for Google Analytics
18741
+ if (typeof window.ga !== 'undefined') {
18742
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18743
+ }
18744
+ // Check for Google Analytics 4
18745
+ if (typeof window.gtag !== 'undefined') {
18746
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18747
+ }
18748
+ // Check for Google Tag Manager
18749
+ if (typeof window.google_tag_manager !== 'undefined') {
18750
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18751
+ }
18752
+ // @TODO: Add support for other analytics tools
18753
+ // Check for Heap Analytics
18754
+ // Check for Mixpanel
18755
+ // Check for Woopra
18756
+ // Check for Segment
18757
+ // Check for Amplitude
18758
+ return analyticsTool;
18759
+ }
18760
+ }
18761
+
18762
+ class EventService {
18763
+ constructor() {
18764
+ this.pubSubService = PubsubService.getInstance();
18765
+ this.localStorageService = LocalStorageService.getInstance();
18766
+ this.activeSpots = new Map();
18767
+ this.spotStates = new Map();
18768
+ this.intersectionObserver = new IntersectionObserverService();
18769
+ // Start the user monitor, which will track and check user interactions
18770
+ MonitorService.getInstance().start();
18893
18771
  }
18894
-
18895
- .${prefix}__header {
18896
- font-size: 16px;
18897
- color: ${textColor};
18898
- line-height: 20px;
18899
- font-weight: 400;
18900
- font-family: "Source Sans 3", system-ui;
18901
- font-style: normal;
18902
- margin: 0;
18903
- padding: 15px 10%;
18772
+ static getInstance() {
18773
+ if (!EventService.instance) {
18774
+ EventService.instance = new EventService();
18775
+ }
18776
+ return EventService.instance;
18904
18777
  }
18905
-
18906
- @container (min-width: 640px) {
18907
- .${prefix} {
18908
- background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18909
- }
18778
+ subscribe(eventType, callback) {
18779
+ return this.pubSubService.subscribe(eventType, callback);
18910
18780
  }
18911
-
18912
- @container (min-width: 768px) {
18913
- .${prefix}__header {
18914
- font-size: 22px;
18915
- }
18781
+ publish(eventType, data) {
18782
+ this.pubSubService.publish(eventType, data);
18916
18783
  }
18917
-
18918
- @container (min-width: 1024px) {
18919
- .${prefix}__header {
18920
- font-size: 24px;
18921
- }
18784
+ registerSpot(params) {
18785
+ const { placementId, spot, spotElement } = params;
18786
+ this.activeSpots.set(placementId, { spotElement });
18787
+ // Fire impression event
18788
+ this.fireImpressionEvent(placementId, spot);
18789
+ // Handle intersection observer
18790
+ this.handleIntersectionObserver(placementId, spot, spotElement);
18791
+ // Attach click event listener
18792
+ spotElement.addEventListener('click', async () => await this.handleClick(params));
18922
18793
  }
18923
-
18924
- @container (min-width: 1280px) {
18925
- .${prefix}__header {
18926
- font-size: 28px;
18927
- }
18794
+ unregisterSpot(placementId) {
18795
+ const placementIdClean = placementId.replace('#', '');
18796
+ const spotData = this.activeSpots.get(placementIdClean);
18797
+ if (!spotData) {
18798
+ this.handleSpotState(placementIdClean, {
18799
+ state: {
18800
+ error: `Active spot with placementId ${placementIdClean} not found.`,
18801
+ },
18802
+ });
18803
+ return;
18804
+ }
18805
+ this.intersectionObserver.unobserve(spotData.spotElement);
18806
+ this.handleSpotState(placementIdClean, {
18807
+ dom: {
18808
+ spotElement: undefined,
18809
+ visibleOnViewport: false,
18810
+ },
18811
+ state: {
18812
+ unmounted: true,
18813
+ mounted: false,
18814
+ },
18815
+ });
18816
+ this.activeSpots.delete(placementIdClean);
18817
+ const placementElement = document.getElementById(placementIdClean);
18818
+ if (!placementElement) {
18819
+ this.handleSpotState(placementIdClean, {
18820
+ state: {
18821
+ error: `Placement element with id ${placementIdClean} not found.`,
18822
+ },
18823
+ });
18824
+ return;
18825
+ }
18826
+ placementElement.innerHTML = '';
18928
18827
  }
18929
- </style>
18930
- `;
18931
- };
18932
- function rbNavigationBannerTemplate(spot, config) {
18933
- const { prefix = '' } = config;
18934
- return `
18935
- ${GFONT_PRECONNECT}
18936
- ${GFONT_SOURCE_SANS_3}
18937
- ${STYLES$2(spot, config)}
18938
- <div class="${prefix}">
18939
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18940
- </div>
18941
- `;
18942
- }
18943
-
18944
- const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18945
- const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18946
- return `
18947
- <style>
18948
- .${prefix} {
18949
- width: 100%;
18950
- height: 100%;
18951
- display: flex;
18952
- flex-direction: column;
18953
- justify-content: flex-end;
18954
- background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18955
- background-size: cover;
18956
- background-repeat: no-repeat;
18957
- background-position: center;
18958
- border-radius: 5px;
18959
- overflow: hidden;
18960
- cursor: pointer;
18961
- position: relative;
18962
- }
18963
-
18964
- .${prefix}__header {
18965
- font-size: 12px;
18966
- color: ${textColor};
18967
- line-height: 16px;
18968
- font-family: "Source Sans 3", system-ui;
18969
- font-style: normal;
18970
- font-weight: 400;
18971
- margin: 0;
18972
- padding: 10px;
18973
- }
18974
-
18975
- @container (min-width: 640px) {
18976
- .${prefix} {
18977
- background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18828
+ /**
18829
+ * Updates the state of a spot.
18830
+ *
18831
+ * @param {string} placementId - The placement ID of the spot.
18832
+ * @param {Partial<ILifecycleState>} updates - The updates to apply to the spot state.
18833
+ * @param {boolean} publish - Whether to publish the updated state.
18834
+ *
18835
+ * @returns {void}
18836
+ */
18837
+ handleSpotState(placementId, updates, publish = true) {
18838
+ let currentState = this.spotStates.get(placementId);
18839
+ if (!currentState) {
18840
+ currentState = {
18841
+ identifier: {
18842
+ placementId,
18843
+ spotId: '',
18844
+ spotType: '',
18845
+ },
18846
+ dom: {
18847
+ spotElement: undefined,
18848
+ visibleOnViewport: false,
18849
+ },
18850
+ state: {
18851
+ mounted: false,
18852
+ unmounted: false,
18853
+ loading: false,
18854
+ error: undefined,
18855
+ },
18856
+ displayConfig: {
18857
+ isCarousel: false,
18858
+ isCarouselItem: false,
18859
+ isSingleItem: false,
18860
+ },
18861
+ };
18978
18862
  }
18979
- }
18980
- </style>
18981
- `;
18982
- };
18983
- function rbSmallCategoryImageToutTemplate(spot, config) {
18984
- const { prefix = '' } = config;
18985
- return `
18986
- ${GFONT_PRECONNECT}
18987
- ${GFONT_CORMORANT}
18988
- ${STYLES$1(spot, config)}
18989
- <div class="${prefix}">
18990
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18991
- </div>
18992
- `;
18993
- }
18994
-
18995
- const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
18996
- <style>
18997
- .${prefix} {
18998
- width: 100%;
18999
- height: 100%;
19000
- background-color: ${backgroundColor};
19001
- cursor: pointer;
19002
- display: flex;
19003
- flex-direction: column;
19004
- border-radius: 5px;
18863
+ const merged = this.deepMerge(currentState, updates);
18864
+ this.spotStates.set(placementId, merged);
18865
+ if (publish) {
18866
+ this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18867
+ }
18868
+ }
18869
+ deepMerge(current, updates) {
18870
+ return {
18871
+ identifier: updates.identifier
18872
+ ? { ...current.identifier, ...updates.identifier }
18873
+ : current.identifier,
18874
+ dom: updates.dom ? { ...current.dom, ...updates.dom } : current.dom,
18875
+ state: updates.state ? { ...current.state, ...updates.state } : current.state,
18876
+ displayConfig: updates.displayConfig
18877
+ ? { ...current.displayConfig, ...updates.displayConfig }
18878
+ : current.displayConfig,
18879
+ };
18880
+ }
18881
+ async handleClick({ placementId, spot }) {
18882
+ var _a, _b, _c;
18883
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18884
+ eventType: RMN_SPOT_EVENT.CLICK,
18885
+ placementId,
18886
+ spotId: spot.id,
18887
+ });
18888
+ await fireEvent({
18889
+ event: RMN_SPOT_EVENT.CLICK,
18890
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18891
+ });
18892
+ // Save spot to local storage for event tracking
18893
+ this.localStorageService.setSpot(spot.id, {
18894
+ placementId,
18895
+ spotId: spot.id,
18896
+ spotType: spot.spot,
18897
+ events: spot.events,
18898
+ productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 2, 3],
18899
+ });
19005
18900
  }
19006
-
19007
- .${prefix}__image {
19008
- width: 100%;
19009
- height: 100%;
19010
- background-image: url("${mobilePrimaryImage}");
19011
- background-size: cover;
19012
- background-repeat: no-repeat;
19013
- background-position: center;
19014
- border-radius: 5px;
18901
+ handleIntersectionObserver(placementId, _spot, spotElement) {
18902
+ const spotIsVisibleCallback = async () => {
18903
+ this.intersectionObserver.unobserve(spotElement);
18904
+ this.handleSpotState(placementId, {
18905
+ dom: {
18906
+ spotElement,
18907
+ visibleOnViewport: true,
18908
+ },
18909
+ });
18910
+ };
18911
+ this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
19015
18912
  }
19016
-
19017
- .${prefix}__text {
19018
- text-align: left;
19019
- display: flex;
19020
- flex-direction: row;
19021
- align-items: flex-start;
19022
- justify-content: flex-start;
19023
- width: 100%;
19024
- height: fit-content;
19025
- position: relative;
18913
+ fireImpressionEvent(placementId, spot) {
18914
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18915
+ eventType: RMN_SPOT_EVENT.IMPRESSION,
18916
+ placementId,
18917
+ spotId: spot.id,
18918
+ });
18919
+ (async () => {
18920
+ var _a, _b;
18921
+ await fireEvent({
18922
+ event: RMN_SPOT_EVENT.IMPRESSION,
18923
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18924
+ });
18925
+ })();
19026
18926
  }
18927
+ }
19027
18928
 
19028
- .${prefix}__header {
19029
- font-size: 12px;
19030
- color: ${textColor};
19031
- padding-top: 5px;
19032
- line-height: 16px;
19033
- font-family: "Source Sans 3", system-ui;
19034
- font-style: normal;
19035
- font-weight: 400;
19036
- margin: 0;
19037
- }
18929
+ const SELECTION_API_PATH = '/spots/selection';
19038
18930
 
19039
- @container (min-width: 640px) {
19040
- .${prefix}__image {
19041
- background-image: url("${primaryImage}");
19042
- }
18931
+ class SelectionService extends BaseApi {
18932
+ constructor(auth) {
18933
+ super(auth);
18934
+ }
18935
+ static getInstance(auth) {
18936
+ return SingletonManager.getInstance('SelectionService', () => new SelectionService(auth));
18937
+ }
18938
+ /**
18939
+ * Makes a selection request on our server based on the provided data.
18940
+ *
18941
+ * @param {ISpotSelectionParams} data - Spots selection parameters.
18942
+ *
18943
+ * @return {Promise<ISpots | { error: string }>} - The spots response object.
18944
+ */
18945
+ async spotSelection(data) {
18946
+ const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
18947
+ if (isErr) {
18948
+ return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
18949
+ }
18950
+ if (isOk && val && val.data && (val === null || val === void 0 ? void 0 : val.refresh.token)) {
18951
+ this.authInfo.authenticated = true;
18952
+ this.authInfo.token = val.refresh.token;
18953
+ return val.data.spots;
18954
+ }
18955
+ return { error: 'Spot selection response was not successful' };
19043
18956
  }
19044
- </style>
19045
- `;
19046
- function rbSmallDiscoverToutTemplate(spot, config) {
19047
- const { prefix = '' } = config;
19048
- return `
19049
- ${GFONT_PRECONNECT}
19050
- ${GFONT_CORMORANT}
19051
- ${STYLES(spot, config)}
19052
- <div class="${prefix}">
19053
- <div class="${prefix}__image"></div>
19054
- <div class="${prefix}__text">
19055
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
19056
- </div>
19057
- </div>
19058
- `;
19059
18957
  }
19060
18958
 
19061
- /**
19062
- * Returns the HTML element for the given spot.
19063
- *
19064
- * @param {ISpot} spot - The spot object.
19065
- * @param {ISpotTemplateConfig} config - The spot template configuration.
19066
- *
19067
- * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
19068
- */
19069
- const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
19070
- const templates = {
19071
- // Reserve Bar Spot Templates
19072
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: {
19073
- rbHomepageHeroThreeTile: rbHomepageHeroThreeTileTemplate,
18959
+ const SPOT_EVENTS_EXAMPLE = [
18960
+ {
18961
+ event: RMN_SPOT_EVENT.CLICK,
18962
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwidXIiOm51bGx9&s=hWz37kbxi_u95EVNn2aoQhc5Aas',
18963
+ },
18964
+ {
18965
+ event: RMN_SPOT_EVENT.IMPRESSION,
18966
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiYmEiOjEsImZxIjowfQ&s=djoysjCimurf-5T11AlNAwwLSS8',
18967
+ },
18968
+ {
18969
+ event: RMN_SPOT_EVENT.PURCHASE,
18970
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjU5fQ&s=AAPAw-3SfZ0JMzjEGFSwt9L-2S4',
18971
+ },
18972
+ {
18973
+ event: RMN_SPOT_EVENT.ADD_TO_CART,
18974
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYwfQ&s=uzQFcjgL7m9XqUG8FvTPVN5YkZY',
18975
+ },
18976
+ {
18977
+ event: RMN_SPOT_EVENT.ADD_TO_WISHLIST,
18978
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYzfQ&s=m3ISU_iIy-OFtXrTKpI6cJAEC0k',
18979
+ },
18980
+ {
18981
+ event: RMN_SPOT_EVENT.BUY_NOW,
18982
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
18983
+ },
18984
+ ];
18985
+ ({
18986
+ rbHomepageHero: [
18987
+ {
18988
+ id: 'abc123',
18989
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18990
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18991
+ width: 1140,
18992
+ height: 640,
18993
+ header: 'Premium Wine Collection',
18994
+ description: 'Discover our exclusive selection of vintage wines',
18995
+ ctaText: 'Shop Wines',
18996
+ textColor: '#ffffff',
18997
+ ctaTextColor: '#ffffff',
18998
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Collection',
18999
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Wine',
19000
+ events: SPOT_EVENTS_EXAMPLE,
19001
+ productIds: [1, 2, 3],
19002
+ },
19003
+ {
19004
+ id: 'jkl012',
19005
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19006
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19007
+ width: 1140,
19008
+ height: 640,
19009
+ header: 'Whiskey Collection',
19010
+ description: 'Premium whiskeys from around the world',
19011
+ ctaText: 'Shop Whiskeys',
19012
+ textColor: '#ffffff',
19013
+ backgroundColor: '#2c1810',
19014
+ ctaTextColor: '#2c1810',
19015
+ primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey',
19016
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
19017
+ events: SPOT_EVENTS_EXAMPLE,
19018
+ productIds: [10, 11],
19019
+ },
19020
+ {
19021
+ id: 'stu901',
19022
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19023
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19024
+ width: 1140,
19025
+ height: 640,
19026
+ header: 'Summer Cocktails',
19027
+ description: 'Essential spirits for summer mixing',
19028
+ ctaText: 'Mix It Up',
19029
+ textColor: '#ffffff',
19030
+ backgroundColor: '#4d3a0a',
19031
+ ctaTextColor: '#4d3a0a',
19032
+ primaryImage: 'https://placehold.co/1140x640/png?text=Cocktails',
19033
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mixers',
19034
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktails',
19035
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mixers',
19036
+ events: SPOT_EVENTS_EXAMPLE,
19037
+ productIds: [16, 17, 18],
19038
+ },
19039
+ {
19040
+ id: 'def456',
19041
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19042
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19043
+ width: 1140,
19044
+ height: 640,
19045
+ header: 'Craft Beer Festival',
19046
+ description: 'Local breweries and exclusive releases',
19047
+ ctaText: 'Explore Beers',
19048
+ textColor: '#ffffff',
19049
+ ctaTextColor: '#ffffff',
19050
+ primaryImage: 'https://placehold.co/1140x640/png?text=Beer+Festival',
19051
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Beer',
19052
+ events: SPOT_EVENTS_EXAMPLE,
19053
+ productIds: [4, 5, 6],
19054
+ },
19055
+ {
19056
+ id: 'mno345',
19057
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19058
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19059
+ width: 1140,
19060
+ height: 640,
19061
+ header: 'Champagne Selection',
19062
+ description: 'Finest champagnes for every occasion',
19063
+ ctaText: 'View Champagnes',
19064
+ textColor: '#ffffff',
19065
+ backgroundColor: '#1a1a1a',
19066
+ ctaTextColor: '#1a1a1a',
19067
+ primaryImage: 'https://placehold.co/1140x640/png?text=Champagne',
19068
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Champagne',
19069
+ events: SPOT_EVENTS_EXAMPLE,
19070
+ productIds: [12, 13],
19071
+ },
19072
+ {
19073
+ id: 'vwx234',
19074
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19075
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19076
+ width: 1140,
19077
+ height: 640,
19078
+ header: 'Wine Regions',
19079
+ description: 'Explore wines from top regions',
19080
+ ctaText: 'Tour Regions',
19081
+ textColor: '#ffffff',
19082
+ backgroundColor: '#4d0a0a',
19083
+ ctaTextColor: '#4d0a0a',
19084
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Regions',
19085
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Vineyards',
19086
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Regions',
19087
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Vineyards',
19088
+ events: SPOT_EVENTS_EXAMPLE,
19089
+ productIds: [19, 20, 21],
19074
19090
  },
19075
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE]: {
19076
- rbHomepageHeroTwoTile: rbHomepageHeroTwoTileTemplate,
19091
+ {
19092
+ id: 'ghi789',
19093
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19094
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19095
+ width: 1140,
19096
+ height: 640,
19097
+ header: 'Rare Spirits',
19098
+ description: 'Limited edition spirits collection',
19099
+ ctaText: 'View Collection',
19100
+ textColor: '#ffffff',
19101
+ ctaTextColor: '#ffffff',
19102
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rare+Spirits',
19103
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Spirits',
19104
+ events: SPOT_EVENTS_EXAMPLE,
19105
+ productIds: [7, 8, 9],
19077
19106
  },
19078
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE]: {
19079
- rbHomepageHeroFullImage: rbHomepageHeroFullImageTemplate,
19107
+ {
19108
+ id: 'pqr678',
19109
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19110
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19111
+ width: 1140,
19112
+ height: 640,
19113
+ header: 'Gin Collection',
19114
+ description: 'Artisanal gins and botanicals',
19115
+ ctaText: 'Explore Gins',
19116
+ textColor: '#ffffff',
19117
+ backgroundColor: '#0a4d4d',
19118
+ ctaTextColor: '#0a4d4d',
19119
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gin',
19120
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gin',
19121
+ events: SPOT_EVENTS_EXAMPLE,
19122
+ productIds: [14, 15],
19080
19123
  },
19081
- [RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT]: {
19082
- rbLargeCategoryImageTout: rbLargeCategoryImageToutTemplate,
19124
+ {
19125
+ id: 'yz5678',
19126
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19127
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19128
+ width: 1140,
19129
+ height: 640,
19130
+ header: 'Tequila Collection',
19131
+ description: 'Premium tequilas and mezcals',
19132
+ ctaText: 'Shop Tequila',
19133
+ textColor: '#ffffff',
19134
+ backgroundColor: '#0a4d2b',
19135
+ ctaTextColor: '#0a4d2b',
19136
+ primaryImage: 'https://placehold.co/1140x640/png?text=Tequila',
19137
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mezcal',
19138
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Tequila',
19139
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mezcal',
19140
+ events: SPOT_EVENTS_EXAMPLE,
19141
+ productIds: [22, 23, 24],
19083
19142
  },
19084
- [RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT]: {
19085
- rbSmallDiscoverTout: rbSmallDiscoverToutTemplate,
19143
+ ],
19144
+ rbHomepageHeroFullImage: [
19145
+ {
19146
+ id: 'hjk567',
19147
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19148
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19149
+ width: 1140,
19150
+ height: 640,
19151
+ header: 'Holiday Gift Guide',
19152
+ description: 'Perfect spirits for every occasion',
19153
+ ctaText: 'Shop Gifts',
19154
+ textColor: '#ffffff',
19155
+ ctaTextColor: '#ffffff',
19156
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
19157
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
19158
+ events: SPOT_EVENTS_EXAMPLE,
19159
+ productIds: [25, 26],
19160
+ },
19161
+ {
19162
+ id: 'qwe890',
19163
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19164
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19165
+ width: 1140,
19166
+ height: 640,
19167
+ header: 'Summer Wine Festival',
19168
+ description: 'Refreshing wines for summer',
19169
+ ctaText: 'Shop Festival',
19170
+ textColor: '#ffffff',
19171
+ ctaTextColor: '#ffffff',
19172
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
19173
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
19174
+ events: SPOT_EVENTS_EXAMPLE,
19175
+ productIds: [27, 28],
19176
+ },
19177
+ ],
19178
+ rbHomepageHeroTwoTile: [
19179
+ {
19180
+ id: 'iop987',
19181
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19182
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19183
+ width: 1140,
19184
+ height: 640,
19185
+ header: 'Bourbon Selection',
19186
+ description: "Kentucky's finest bourbons",
19187
+ ctaText: 'Shop Bourbon',
19188
+ textColor: '#ffffff',
19189
+ backgroundColor: '#2c1810',
19190
+ ctaTextColor: '#2c1810',
19191
+ primaryImage: 'https://placehold.co/1140x640/png?text=Bourbon',
19192
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Bourbon',
19193
+ events: SPOT_EVENTS_EXAMPLE,
19194
+ productIds: [29, 30],
19195
+ },
19196
+ {
19197
+ id: 'lkj012',
19198
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19199
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19200
+ width: 1140,
19201
+ height: 640,
19202
+ header: 'Vodka Collection',
19203
+ description: 'Premium vodkas from around the world',
19204
+ ctaText: 'Shop Vodka',
19205
+ textColor: '#ffffff',
19206
+ backgroundColor: '#1a1a1a',
19207
+ ctaTextColor: '#1a1a1a',
19208
+ primaryImage: 'https://placehold.co/1140x640/png?text=Vodka',
19209
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Vodka',
19210
+ events: SPOT_EVENTS_EXAMPLE,
19211
+ productIds: [31, 32],
19212
+ },
19213
+ ],
19214
+ rbHomepageHeroThreeTile: [
19215
+ {
19216
+ id: 'bnm345',
19217
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19218
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19219
+ width: 1140,
19220
+ height: 640,
19221
+ header: 'Rum Collection',
19222
+ description: 'Caribbean and craft rums',
19223
+ ctaText: 'Shop Rum',
19224
+ textColor: '#ffffff',
19225
+ backgroundColor: '#4d3a0a',
19226
+ ctaTextColor: '#4d3a0a',
19227
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rum',
19228
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Craft+Rum',
19229
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Rum',
19230
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Craft',
19231
+ events: SPOT_EVENTS_EXAMPLE,
19232
+ productIds: [33, 34],
19233
+ },
19234
+ {
19235
+ id: 'fgh678',
19236
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19237
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19238
+ width: 1140,
19239
+ height: 640,
19240
+ header: 'Scotch Selection',
19241
+ description: 'Single malts and blends',
19242
+ ctaText: 'Shop Scotch',
19243
+ textColor: '#ffffff',
19244
+ backgroundColor: '#4d0a0a',
19245
+ ctaTextColor: '#4d0a0a',
19246
+ primaryImage: 'https://placehold.co/1140x640/png?text=Scotch',
19247
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Single+Malts',
19248
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Scotch',
19249
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Malts',
19250
+ events: SPOT_EVENTS_EXAMPLE,
19251
+ productIds: [35, 36],
19252
+ },
19253
+ ],
19254
+ rbLargeCategoryImageTout: [
19255
+ {
19256
+ id: 'cde567',
19257
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19258
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19259
+ width: 468,
19260
+ height: 410,
19261
+ header: 'Rare & Limited Edition',
19262
+ description: 'Discover our collection of hard-to-find and limited release spirits.',
19263
+ textColor: '#ffffff',
19264
+ ctaTextColor: '#ffffff',
19265
+ primaryImage: 'https://placehold.co/468x410/png?text=Rare+Spirits',
19266
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
19267
+ ctaText: 'Shop Rare Spirits',
19268
+ events: SPOT_EVENTS_EXAMPLE,
19269
+ productIds: [37, 38, 39, 40, 41],
19270
+ },
19271
+ {
19272
+ id: 'efg789',
19273
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19274
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19275
+ width: 468,
19276
+ height: 410,
19277
+ header: 'Vintage Champagnes',
19278
+ description: 'Explore our prestigious collection of aged champagnes.',
19279
+ textColor: '#ffffff',
19280
+ ctaTextColor: '#ffffff',
19281
+ primaryImage: 'https://placehold.co/468x410/png?text=Vintage+Champagne',
19282
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Champagne',
19283
+ ctaText: 'View Collection',
19284
+ events: SPOT_EVENTS_EXAMPLE,
19285
+ productIds: [42, 43, 44, 45, 46],
19286
+ },
19287
+ {
19288
+ id: 'ghi012',
19289
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19290
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19291
+ width: 468,
19292
+ height: 410,
19293
+ header: 'Small Batch Bourbon',
19294
+ description: 'Hand-selected premium bourbon from craft distilleries.',
19295
+ textColor: '#ffffff',
19296
+ ctaTextColor: '#ffffff',
19297
+ primaryImage: 'https://placehold.co/468x410/png?text=Craft+Bourbon',
19298
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Bourbon',
19299
+ ctaText: 'Explore Bourbon',
19300
+ events: SPOT_EVENTS_EXAMPLE,
19301
+ productIds: [47, 48, 49, 50, 51],
19302
+ },
19303
+ ],
19304
+ rbSmallDiscoverTout: [
19305
+ {
19306
+ id: 'jkl345',
19307
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19308
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19309
+ width: 224,
19310
+ height: 378,
19311
+ header: 'Château Margaux 2015 Bordeaux',
19312
+ textColor: '#ffffff',
19313
+ primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
19314
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
19315
+ events: SPOT_EVENTS_EXAMPLE,
19316
+ productIds: [52, 53, 54, 55, 56],
19317
+ },
19318
+ {
19319
+ id: 'lmn678',
19320
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19321
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19322
+ width: 224,
19323
+ height: 378,
19324
+ header: 'Macallan 25 Year',
19325
+ textColor: '#ffffff',
19326
+ primaryImage: 'https://placehold.co/224x378/png?text=Macallan+25',
19327
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Macallan',
19328
+ events: SPOT_EVENTS_EXAMPLE,
19329
+ productIds: [57, 58, 59, 60, 61],
19330
+ },
19331
+ {
19332
+ id: 'nop901',
19333
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19334
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19335
+ width: 224,
19336
+ height: 378,
19337
+ header: 'Louis XIII Cognac',
19338
+ textColor: '#ffffff',
19339
+ primaryImage: 'https://placehold.co/224x378/png?text=Louis+XIII',
19340
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Louis+XIII',
19341
+ events: SPOT_EVENTS_EXAMPLE,
19342
+ productIds: [62, 63, 64, 65, 66],
19086
19343
  },
19087
- [RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT]: {
19088
- rbSmallCategoryImageTout: rbSmallCategoryImageToutTemplate,
19344
+ ],
19345
+ rbSmallCategoryImageTout: [
19346
+ {
19347
+ id: 'qrs234',
19348
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19349
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19350
+ width: 224,
19351
+ height: 410,
19352
+ header: 'Japanese Sake',
19353
+ textColor: '#ffffff',
19354
+ primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
19355
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
19356
+ events: SPOT_EVENTS_EXAMPLE,
19357
+ productIds: [67, 68, 69, 70, 71],
19089
19358
  },
19090
- [RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK]: {
19091
- rbCollectionBannerWithoutTextBlock: rbCollectionBannerWithoutTextBlockTemplate,
19359
+ {
19360
+ id: 'stu567',
19361
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19362
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19363
+ width: 224,
19364
+ height: 410,
19365
+ header: 'Craft Vermouth',
19366
+ textColor: '#ffffff',
19367
+ primaryImage: 'https://placehold.co/224x410/png?text=Craft+Vermouth',
19368
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Vermouth',
19369
+ events: SPOT_EVENTS_EXAMPLE,
19370
+ productIds: [72, 73, 74, 75, 76],
19092
19371
  },
19093
- [RMN_SPOT_TYPE.RB_NAVIGATION_BANNER]: {
19094
- rbNavigationBanner: rbNavigationBannerTemplate,
19372
+ {
19373
+ id: 'vwx890',
19374
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19375
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19376
+ width: 224,
19377
+ height: 410,
19378
+ header: 'Premium Mezcal',
19379
+ textColor: '#ffffff',
19380
+ primaryImage: 'https://placehold.co/224x410/png?text=Premium+Mezcal',
19381
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Mezcal',
19382
+ events: SPOT_EVENTS_EXAMPLE,
19383
+ productIds: [77, 78, 79, 80, 81],
19095
19384
  },
19096
- [RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: {
19097
- rbProductUpcs: () => '', // No template for this spot type, it will be handled by ReserveBar App.
19385
+ ],
19386
+ rbCollectionBannerWithoutTextBlock: [
19387
+ {
19388
+ id: 'yz1234',
19389
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19390
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19391
+ width: 887,
19392
+ height: 344,
19393
+ primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
19394
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
19395
+ events: SPOT_EVENTS_EXAMPLE,
19396
+ productIds: [82, 83, 84, 85, 86],
19098
19397
  },
19099
- // IAB Standard Spot Templates
19100
- [RMN_SPOT_TYPE.BILLBOARD]: {
19101
- billboardV1: billboardV1Template,
19102
- billboardV2: billboardV2Template,
19103
- billboardV3: billboardV3Template,
19398
+ {
19399
+ id: 'abc567',
19400
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19401
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19402
+ width: 887,
19403
+ height: 344,
19404
+ primaryImage: 'https://placehold.co/887x344/png?text=Holiday+Spirits',
19405
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Holiday+Spirits',
19406
+ events: SPOT_EVENTS_EXAMPLE,
19407
+ productIds: [87, 88, 89, 90, 91],
19104
19408
  },
19105
- [RMN_SPOT_TYPE.LARGE_RECTANGLE]: {
19106
- largeRectangleV1: largeRectangleV1Template,
19409
+ {
19410
+ id: 'def901',
19411
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19412
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19413
+ width: 887,
19414
+ height: 344,
19415
+ primaryImage: 'https://placehold.co/887x344/png?text=Wine+Essentials',
19416
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Wine+Essentials',
19417
+ events: SPOT_EVENTS_EXAMPLE,
19418
+ productIds: [92, 93, 94, 95, 96],
19107
19419
  },
19108
- [RMN_SPOT_TYPE.VERTICAL_RECTANGLE]: {
19109
- verticalRectangleV1: verticalRectangleV1Template,
19420
+ ],
19421
+ rbNavigationBanner: [
19422
+ {
19423
+ id: 'ghi234',
19424
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19425
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19426
+ width: 440,
19427
+ height: 220,
19428
+ header: 'Explore Tequilas',
19429
+ textColor: '#ffffff',
19430
+ primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
19431
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
19432
+ events: SPOT_EVENTS_EXAMPLE,
19433
+ productIds: [97, 98, 99, 100, 101],
19110
19434
  },
19111
- [RMN_SPOT_TYPE.SQUARE]: {
19112
- squareV1: squareV1Template,
19113
- squareV2: squareV2Template,
19435
+ {
19436
+ id: 'jkl678',
19437
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19438
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19439
+ width: 440,
19440
+ height: 220,
19441
+ header: 'Craft Gin Selection',
19442
+ textColor: '#ffffff',
19443
+ primaryImage: 'https://placehold.co/440x220/png?text=Gin+Selection',
19444
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Gin+Selection',
19445
+ events: SPOT_EVENTS_EXAMPLE,
19446
+ productIds: [102, 103, 104, 105, 106],
19114
19447
  },
19115
- [RMN_SPOT_TYPE.LARGE_LEADERBOARD]: {
19116
- largeLeaderboardV1: largeLeaderboardV1Template,
19117
- largeLeaderboardV2: largeLeaderboardV2Template,
19448
+ {
19449
+ id: 'mno012',
19450
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19451
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19452
+ width: 440,
19453
+ height: 220,
19454
+ header: 'Premium Vodka',
19455
+ textColor: '#ffffff',
19456
+ primaryImage: 'https://placehold.co/440x220/png?text=Vodka+Premium',
19457
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Vodka+Premium',
19458
+ events: SPOT_EVENTS_EXAMPLE,
19459
+ productIds: [107, 108, 109, 110, 111],
19118
19460
  },
19119
- [RMN_SPOT_TYPE.WIDE_SKYSCRAPER]: {
19120
- wideSkyscraperV1: wideSkyscraperV1Template,
19461
+ ],
19462
+ });
19463
+ ({
19464
+ banner: [],
19465
+ billboard: [
19466
+ {
19467
+ id: 'kol567',
19468
+ spot: RMN_SPOT_TYPE.BILLBOARD,
19469
+ variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
19470
+ width: 1140,
19471
+ height: 640,
19472
+ header: 'Holiday Gift Guide',
19473
+ description: 'Perfect spirits for every occasion',
19474
+ ctaText: 'Shop Gifts',
19475
+ textColor: '#ffffff',
19476
+ ctaTextColor: '#ffffff',
19477
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
19478
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
19479
+ events: SPOT_EVENTS_EXAMPLE,
19480
+ productIds: [25, 26],
19121
19481
  },
19122
- [RMN_SPOT_TYPE.IN_TEXT]: {
19123
- inTextV1: inTextV1Template,
19482
+ {
19483
+ id: 'hpm390',
19484
+ spot: RMN_SPOT_TYPE.BILLBOARD,
19485
+ variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
19486
+ width: 1140,
19487
+ height: 640,
19488
+ header: 'Summer Wine Festival',
19489
+ description: 'Refreshing wines for summer',
19490
+ ctaText: 'Shop Festival',
19491
+ textColor: '#ffffff',
19492
+ ctaTextColor: '#ffffff',
19493
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
19494
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
19495
+ events: SPOT_EVENTS_EXAMPLE,
19496
+ productIds: [27, 28],
19124
19497
  },
19125
- };
19126
- const spotVariants = templates[spot.spot];
19127
- if (!spotVariants) {
19128
- return null;
19129
- }
19130
- const variantTemplate = spotVariants[spot.variant];
19131
- if (!variantTemplate) {
19132
- return null;
19133
- }
19134
- // Generate a highly unique prefix to avoid conflicts with other elements.
19135
- const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
19136
- const spotHtmlString = variantTemplate(spot, { ...config, prefix });
19137
- return spotHtmlStringToElement(spotHtmlString);
19138
- };
19139
-
19140
- // For the moment, we will only focus on sites that use Google Analytics,
19141
- // but we will add support for other analytics tools in the future.
19142
- var AnalyticsTool;
19143
- (function (AnalyticsTool) {
19144
- AnalyticsTool["GoogleAnalytics"] = "google-analytics";
19145
- AnalyticsTool["Other"] = "Other";
19146
- })(AnalyticsTool || (AnalyticsTool = {}));
19147
-
19148
- class DataLayerMonitor {
19149
- constructor() {
19150
- if (!window.dataLayer) {
19151
- return;
19152
- }
19153
- this.originalPush = window.dataLayer.push;
19154
- }
19155
- static getInstance() {
19156
- if (!DataLayerMonitor.instance) {
19157
- DataLayerMonitor.instance = new DataLayerMonitor();
19158
- }
19159
- return DataLayerMonitor.instance;
19160
- }
19161
- setListener(listener) {
19162
- this.listener = listener;
19163
- }
19164
- start() {
19165
- window.dataLayer.push = (...args) => {
19166
- const result = this.originalPush.apply(window.dataLayer, args);
19167
- const pushedEvent = args[0];
19168
- if (this.listener) {
19169
- const normalizedData = this.cleanEventData(pushedEvent);
19170
- if (normalizedData) {
19171
- this.listener(normalizedData);
19172
- }
19173
- }
19174
- return result;
19175
- };
19176
- }
19177
- cleanEventData(data) {
19178
- const eventName = getEventTypeFromRawEvent(data.event);
19179
- if (!eventName) {
19180
- return null;
19181
- }
19182
- const productIds = extractDeepIds(data);
19183
- return {
19184
- event: eventName,
19185
- productIds,
19186
- };
19187
- }
19188
- stop() {
19189
- if (this.originalPush) {
19190
- window.dataLayer.push = this.originalPush;
19191
- }
19192
- this.listener = undefined;
19193
- }
19194
- }
19498
+ ],
19499
+ button2: [],
19500
+ featurePhoneLargeBanner: [],
19501
+ featurePhoneMediumBanner: [],
19502
+ featurePhoneSmallBanner: [],
19503
+ halfPage: [],
19504
+ inText: [],
19505
+ largeLeaderboard: [],
19506
+ largeRectangle: [],
19507
+ leaderboard: [],
19508
+ mediumRectangle: [],
19509
+ microBar: [],
19510
+ mobilePhoneInterstitial1: [],
19511
+ mobilePhoneInterstitial2: [],
19512
+ mobilePhoneInterstitial3: [],
19513
+ popUp: [],
19514
+ portrait: [],
19515
+ rbProductUpcs: [],
19516
+ skyscraper: [],
19517
+ smallRectangle: [],
19518
+ smallSquare: [],
19519
+ smartphoneBanner1: [],
19520
+ smartphoneBanner2: [],
19521
+ square: [],
19522
+ verticalBanner: [],
19523
+ verticalRectangle: [],
19524
+ wideSkyscraper: [],
19525
+ });
19195
19526
 
19196
- // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
19197
- // window.rmnDataLayer = window.rmnDataLayer || [];
19198
- class MonitorService {
19199
- constructor() {
19200
- const analyticsTool = this.detectAnalyticsTool();
19201
- switch (analyticsTool) {
19202
- case AnalyticsTool.GoogleAnalytics:
19203
- this.implementedMonitor = DataLayerMonitor.getInstance();
19204
- break;
19205
- case AnalyticsTool.Other:
19206
- default:
19207
- console.warn('This site uses an unsupported analytics tool.');
19208
- break;
19209
- }
19210
- if (analyticsTool === AnalyticsTool.Other) {
19211
- return;
19212
- }
19213
- this.pubSubService = PubsubService.getInstance();
19214
- this.localStorageService = LocalStorageService.getInstance();
19215
- }
19216
- static getInstance() {
19217
- if (!MonitorService.instance) {
19218
- MonitorService.instance = new MonitorService();
19219
- }
19220
- return MonitorService.instance;
19221
- }
19222
- start() {
19223
- if (!this.implementedMonitor)
19224
- return;
19225
- this.implementedMonitor.setListener(async (eventData) => {
19226
- var _a;
19227
- await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
19228
- });
19229
- this.implementedMonitor.start();
19230
- }
19231
- async matchAndFireEvent(eventData, spots) {
19232
- var _a, _b;
19233
- if (!spots)
19234
- return;
19235
- const eventProductIds = new Set(eventData.productIds);
19236
- for (const spot of Object.values(spots)) {
19237
- if (!spot.productIds.length)
19238
- continue;
19239
- const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(productId));
19240
- if (hasCommonProductIds) {
19241
- if (Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
19242
- await this.fireAndPublishSpotEvent({
19243
- spotEvent: eventData.event,
19244
- eventUrl: (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
19245
- placementId: spot.placementId,
19246
- spotId: spot.spotId,
19247
- });
19248
- }
19249
- }
19250
- }
19251
- }
19252
- async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
19253
- await fireEvent({
19254
- event: spotEvent,
19255
- eventUrl,
19256
- });
19257
- if (!this.pubSubService)
19258
- return;
19259
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19260
- eventType: spotEvent,
19261
- placementId,
19262
- spotId,
19263
- });
19264
- }
19265
- detectAnalyticsTool() {
19266
- let analyticsTool = AnalyticsTool.Other;
19267
- // Check for Google Analytics
19268
- if (typeof window.ga !== 'undefined') {
19269
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19270
- }
19271
- // Check for Google Analytics 4
19272
- if (typeof window.gtag !== 'undefined') {
19273
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19274
- }
19275
- // Check for Google Tag Manager
19276
- if (typeof window.google_tag_manager !== 'undefined') {
19277
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19278
- }
19279
- // @TODO: Add support for other analytics tools
19280
- // Check for Heap Analytics
19281
- // Check for Mixpanel
19282
- // Check for Woopra
19283
- // Check for Segment
19284
- // Check for Amplitude
19285
- return analyticsTool;
19527
+ /**
19528
+ * Checks if the current environment is a browser environment.
19529
+ *
19530
+ * @return {boolean} - Whether the current environment is a browser environment.
19531
+ */
19532
+ function isBrowserEnvironment() {
19533
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
19534
+ console.warn('LiquidCommerce Rmn Sdk: Methods which create elements are only available in browser environments.');
19535
+ return false;
19286
19536
  }
19537
+ return true;
19287
19538
  }
19288
-
19289
- class EventService {
19290
- constructor() {
19291
- this.pubSubService = PubsubService.getInstance();
19292
- this.localStorageService = LocalStorageService.getInstance();
19293
- this.activeSpots = new Map();
19294
- this.spotStates = new Map();
19295
- this.intersectionObserver = new IntersectionObserverService();
19296
- // Start the user monitor, which will track and check user interactions
19297
- MonitorService.getInstance().start();
19298
- }
19299
- static getInstance() {
19300
- if (!EventService.instance) {
19301
- EventService.instance = new EventService();
19302
- }
19303
- return EventService.instance;
19304
- }
19305
- subscribe(eventType, callback) {
19306
- return this.pubSubService.subscribe(eventType, callback);
19307
- }
19308
- publish(eventType, data) {
19309
- this.pubSubService.publish(eventType, data);
19310
- }
19311
- registerSpot(params) {
19312
- const { placementId, spot, spotElement } = params;
19313
- this.activeSpots.set(placementId, { spotElement });
19314
- // Fire impression event
19315
- this.fireImpressionEvent(placementId, spot);
19316
- // Handle intersection observer
19317
- this.handleIntersectionObserver(placementId, spot, spotElement);
19318
- // Attach click event listener
19319
- spotElement.addEventListener('click', async () => await this.handleClick(params));
19320
- }
19321
- unregisterSpot(placementId) {
19322
- const placementIdClean = placementId.replace('#', '');
19323
- const spotData = this.activeSpots.get(placementIdClean);
19324
- if (!spotData) {
19325
- this.handleSpotState(placementIdClean, {
19326
- state: {
19327
- error: `Active spot with placementId ${placementIdClean} not found.`,
19328
- },
19329
- });
19330
- return;
19331
- }
19332
- this.intersectionObserver.unobserve(spotData.spotElement);
19333
- this.handleSpotState(placementIdClean, {
19334
- dom: {
19335
- spotElement: undefined,
19336
- visibleOnViewport: false,
19337
- },
19338
- state: {
19339
- unmounted: true,
19340
- mounted: false,
19341
- },
19342
- });
19343
- this.activeSpots.delete(placementIdClean);
19344
- const placementElement = document.getElementById(placementIdClean);
19345
- if (!placementElement) {
19346
- this.handleSpotState(placementIdClean, {
19539
+ /**
19540
+ * Validates the inject data by preventing duplicate placement ids and non-existent spot types.
19541
+ *
19542
+ * @param {IInjectSpotElement[]} inject - The inject data.
19543
+ * @return {IInjectSpotElement[]} - The validated inject data.
19544
+ */
19545
+ function validateInjectData(inject) {
19546
+ const eventService = EventService.getInstance();
19547
+ const placementIds = new Set();
19548
+ const validSpotTypes = new Set(Object.values(RMN_SPOT_TYPE));
19549
+ const validatedInject = [];
19550
+ for (const item of inject) {
19551
+ const placementId = item.placementId.replace('#', '');
19552
+ // Check for duplicate placement ids
19553
+ if (placementIds.has(placementId)) {
19554
+ eventService.handleSpotState(placementId, {
19347
19555
  state: {
19348
- error: `Placement element with id ${placementIdClean} not found.`,
19556
+ error: `Duplicate placement id (${placementId}) found. Please provide a unique placement id for each spot element.`,
19349
19557
  },
19350
19558
  });
19351
- return;
19352
- }
19353
- placementElement.innerHTML = '';
19354
- }
19355
- /**
19356
- * Updates the state of a spot.
19357
- *
19358
- * @param {string} placementId - The placement ID of the spot.
19359
- * @param {Partial<ILifecycleState>} updates - The updates to apply to the spot state.
19360
- * @param {boolean} publish - Whether to publish the updated state.
19361
- *
19362
- * @returns {void}
19363
- */
19364
- handleSpotState(placementId, updates, publish = true) {
19365
- let currentState = this.spotStates.get(placementId);
19366
- if (!currentState) {
19367
- currentState = {
19368
- identifier: {
19369
- placementId,
19370
- spotId: '',
19371
- spotType: '',
19372
- },
19373
- dom: {
19374
- spotElement: undefined,
19375
- visibleOnViewport: false,
19376
- },
19377
- state: {
19378
- mounted: false,
19379
- unmounted: false,
19380
- loading: false,
19381
- error: undefined,
19382
- },
19383
- displayConfig: {
19384
- isCarousel: false,
19385
- isCarouselItem: false,
19386
- isSingleItem: false,
19387
- },
19388
- };
19389
- }
19390
- const merged = this.deepMerge(currentState, updates);
19391
- this.spotStates.set(placementId, merged);
19392
- if (publish) {
19393
- this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
19394
- }
19395
- }
19396
- deepMerge(current, updates) {
19397
- return {
19398
- identifier: updates.identifier
19399
- ? { ...current.identifier, ...updates.identifier }
19400
- : current.identifier,
19401
- dom: updates.dom ? { ...current.dom, ...updates.dom } : current.dom,
19402
- state: updates.state ? { ...current.state, ...updates.state } : current.state,
19403
- displayConfig: updates.displayConfig
19404
- ? { ...current.displayConfig, ...updates.displayConfig }
19405
- : current.displayConfig,
19406
- };
19407
- }
19408
- async handleClick({ placementId, spot }) {
19409
- var _a, _b, _c;
19410
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19411
- eventType: RMN_SPOT_EVENT.CLICK,
19412
- placementId,
19413
- spotId: spot.id,
19414
- });
19415
- await fireEvent({
19416
- event: RMN_SPOT_EVENT.CLICK,
19417
- eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
19418
- });
19419
- // Save spot to local storage for event tracking
19420
- this.localStorageService.setSpot(spot.id, {
19421
- placementId,
19422
- spotId: spot.id,
19423
- spotType: spot.spot,
19424
- events: spot.events,
19425
- productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 2, 3],
19426
- });
19427
- }
19428
- handleIntersectionObserver(placementId, _spot, spotElement) {
19429
- const spotIsVisibleCallback = async () => {
19430
- this.intersectionObserver.unobserve(spotElement);
19431
- this.handleSpotState(placementId, {
19432
- dom: {
19433
- spotElement,
19434
- visibleOnViewport: true,
19559
+ continue;
19560
+ }
19561
+ // Check for non-existent spot types
19562
+ if (!validSpotTypes.has(item.spotType)) {
19563
+ eventService.handleSpotState(placementId, {
19564
+ state: {
19565
+ error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
19435
19566
  },
19436
19567
  });
19437
- };
19438
- this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
19439
- }
19440
- fireImpressionEvent(placementId, spot) {
19441
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19442
- eventType: RMN_SPOT_EVENT.IMPRESSION,
19443
- placementId,
19444
- spotId: spot.id,
19445
- });
19446
- (async () => {
19447
- var _a, _b;
19448
- await fireEvent({
19449
- event: RMN_SPOT_EVENT.IMPRESSION,
19450
- eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
19451
- });
19452
- })();
19568
+ continue;
19569
+ }
19570
+ placementIds.add(placementId);
19571
+ validatedInject.push(item);
19453
19572
  }
19573
+ return validatedInject;
19454
19574
  }
19455
-
19456
- const SELECTION_API_PATH = '/spots/selection';
19457
-
19458
- class SelectionService extends BaseApi {
19459
- constructor(auth) {
19460
- super(auth);
19461
- }
19462
- static getInstance(auth) {
19463
- return SingletonManager.getInstance('SelectionService', () => new SelectionService(auth));
19575
+ /**
19576
+ * Clears the placement element by removing all its children.
19577
+ *
19578
+ * @param {string} placementId - The placement id.
19579
+ *
19580
+ * @return {void}
19581
+ */
19582
+ function clearPlacement(placementId) {
19583
+ var _a;
19584
+ (_a = document.getElementById(placementId)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
19585
+ }
19586
+ /**
19587
+ * Prepares the spot placement for rendering by taking care of its styling.
19588
+ *
19589
+ * @param {HTMLElement} placement - The placement element.
19590
+ *
19591
+ * @return {void}
19592
+ */
19593
+ function prepareSpotPlacement(placement) {
19594
+ placement.removeAttribute('style');
19595
+ placement.removeAttribute('class');
19596
+ const styles = {
19597
+ width: '100%',
19598
+ height: '100%',
19599
+ display: 'flex',
19600
+ justifyContent: 'center',
19601
+ };
19602
+ Object.assign(placement.style, styles);
19603
+ }
19604
+ /**
19605
+ * Waits for the DOM to be ready before continuing.
19606
+ *
19607
+ * @return {Promise<void>} - A promise that resolves when the DOM is ready.
19608
+ */
19609
+ async function waitForDOM() {
19610
+ if (!isBrowserEnvironment()) {
19611
+ return;
19464
19612
  }
19465
- /**
19466
- * Makes a selection request on our server based on the provided data.
19467
- *
19468
- * @param {ISpotSelectionParams} data - Spots selection parameters.
19469
- *
19470
- * @return {Promise<ISpots | { error: string }>} - The spots response object.
19471
- */
19472
- async spotSelection(data) {
19473
- const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
19474
- if (isErr) {
19475
- return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
19476
- }
19477
- if (isOk && val && val.data && (val === null || val === void 0 ? void 0 : val.refresh.token)) {
19478
- this.authInfo.authenticated = true;
19479
- this.authInfo.token = val.refresh.token;
19480
- return val.data.spots;
19481
- }
19482
- return { error: 'Spot selection response was not successful' };
19613
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
19614
+ return;
19483
19615
  }
19616
+ return new Promise((resolve) => {
19617
+ document.addEventListener('DOMContentLoaded', () => {
19618
+ resolve();
19619
+ });
19620
+ });
19484
19621
  }
19485
19622
 
19623
+ /**
19624
+ * LiquidCommerce Rmn Client
19625
+ * @class
19626
+ */
19486
19627
  class LiquidCommerceRmnClient {
19487
19628
  constructor(auth) {
19488
19629
  this.selectionService = SelectionService.getInstance(auth);
@@ -19511,14 +19652,11 @@ class LiquidCommerceRmnClient {
19511
19652
  */
19512
19653
  async injectSpotElement(params) {
19513
19654
  var _a;
19514
- if (typeof window === 'undefined' || typeof document === 'undefined') {
19515
- console.warn('LiquidCommerce Rmn Sdk: injectSpotElement method is only available in the browser environment.');
19516
- return;
19517
- }
19655
+ // Wait for the DOM to be ready before continuing, to avoid issues with the spot placements
19656
+ await waitForDOM();
19518
19657
  const config = params.config;
19519
- let inject = params.inject;
19520
19658
  // Handle no spots error state
19521
- if (!inject.length) {
19659
+ if (!params.inject.length) {
19522
19660
  this.eventService.handleSpotState('all', {
19523
19661
  state: {
19524
19662
  error: 'No spot elements provided for injection.',
@@ -19526,49 +19664,32 @@ class LiquidCommerceRmnClient {
19526
19664
  });
19527
19665
  return;
19528
19666
  }
19529
- // Identify the spot elements
19667
+ // Validate inject data
19668
+ const inject = validateInjectData(params.inject);
19530
19669
  for (const item of inject) {
19670
+ // Identify the spot element
19531
19671
  this.eventService.handleSpotState(item.placementId, {
19532
19672
  identifier: {
19533
19673
  placementId: item.placementId,
19534
19674
  spotType: item.spotType,
19535
19675
  },
19536
19676
  }, false);
19537
- }
19538
- // Prevent duplicate placement ids
19539
- const hasDuplicatePlacementIds = this.preventDuplicateSpotPlacementIds(inject);
19540
- if (!hasDuplicatePlacementIds) {
19541
- return;
19542
- }
19543
- // Prevent non-existent spot types
19544
- inject = this.preventNonExistentSpotTypes(inject);
19545
- // Add Intersection Observer to the spot elements to track visibility
19546
- // This is useful for lazy loading the spot elements
19547
- for (const item of inject) {
19548
- const placementId = item.placementId.replace('#', '');
19549
- const placement = document.getElementById(placementId);
19677
+ const placement = document.getElementById(item.placementId);
19678
+ // Handle placement not found error state
19550
19679
  if (!placement) {
19551
- // Handle placement not found error state
19552
19680
  this.eventService.handleSpotState(item.placementId, {
19553
19681
  state: {
19554
- error: `Placement not found for id "${placementId}".`,
19682
+ error: `Placement not found for id "${item.placementId}".`,
19555
19683
  },
19556
19684
  });
19557
19685
  continue;
19558
19686
  }
19559
- // Take over placement styles
19560
- placement.removeAttribute('style');
19561
- placement.removeAttribute('class');
19562
- Object.assign(placement.style, {
19563
- width: '100%',
19564
- height: '100%',
19565
- display: 'flex',
19566
- justifyContent: 'center',
19567
- });
19687
+ prepareSpotPlacement(placement);
19568
19688
  const skeletonElement = this.elementService.createSkeletonElement({
19569
19689
  fluid: (_a = config === null || config === void 0 ? void 0 : config.fluid) !== null && _a !== void 0 ? _a : false,
19570
19690
  spotType: item.spotType,
19571
19691
  });
19692
+ // Handle skeleton loader error state
19572
19693
  if (!skeletonElement) {
19573
19694
  this.eventService.handleSpotState(item.placementId, {
19574
19695
  state: {
@@ -19580,19 +19701,15 @@ class LiquidCommerceRmnClient {
19580
19701
  continue;
19581
19702
  }
19582
19703
  placement.replaceChildren(skeletonElement);
19583
- const spotPlacementIsNear = async () => {
19704
+ const spotPlacementIsNearCallback = async () => {
19584
19705
  var _a;
19585
- // Set the spot element to loading state
19586
- this.eventService.handleSpotState(item.placementId, {
19587
- state: {
19588
- loading: true,
19589
- },
19590
- });
19591
- // Stop observing the placement
19706
+ // Stop observing the placement, as we only need to do this once
19592
19707
  this.intersectionObserver.unobserve(placement);
19708
+ // Set the spot element to loading state
19709
+ this.eventService.handleSpotState(item.placementId, { state: { loading: true } });
19593
19710
  // Make the spot selection request
19594
- const response = await this.spotSelectionRequest({ ...params, inject: [item] });
19595
- // const response = await this.useSpotSelectionExample(inject);
19711
+ const response = await this.injectSpotSelectionRequest({ ...params, inject: [item] });
19712
+ // const response = await useSpotSelectionExample(inject);
19596
19713
  // Handle request error state
19597
19714
  if (typeof response === 'object' && 'error' in response) {
19598
19715
  this.eventService.handleSpotState(item.placementId, {
@@ -19602,13 +19719,13 @@ class LiquidCommerceRmnClient {
19602
19719
  loading: false,
19603
19720
  },
19604
19721
  });
19605
- this.clearPlacement(item.placementId);
19722
+ clearPlacement(item.placementId);
19606
19723
  return;
19607
19724
  }
19608
19725
  const itemConfig = (_a = item.config) !== null && _a !== void 0 ? _a : config;
19609
19726
  const spots = response[item.placementId];
19727
+ // Handle no spots found error state
19610
19728
  if (!(spots === null || spots === void 0 ? void 0 : spots.length)) {
19611
- // Handle no spots found error state
19612
19729
  this.eventService.handleSpotState(item.placementId, {
19613
19730
  state: {
19614
19731
  error: `No spots found for type "${item.spotType}".`,
@@ -19616,22 +19733,105 @@ class LiquidCommerceRmnClient {
19616
19733
  loading: false,
19617
19734
  },
19618
19735
  });
19619
- this.clearPlacement(item.placementId);
19736
+ clearPlacement(item.placementId);
19620
19737
  return;
19621
19738
  }
19622
19739
  // Handle single spot
19623
19740
  if (spots.length === 1) {
19624
- this.injectOneSpotElement(item, placement, spots[0], itemConfig);
19741
+ this.injectOneSpotElement(placement, spots[0], itemConfig);
19625
19742
  }
19626
19743
  // Handle multiple spots (carousel)
19627
19744
  if (spots.length > 1) {
19628
19745
  this.injectCarouselSpotElement(placement, spots, itemConfig);
19629
19746
  }
19630
19747
  };
19631
- this.intersectionObserver.observe(placement, spotPlacementIsNear, { rootMargin: '500px' });
19748
+ /**
19749
+ * Observe the placement element to check if it is near the viewport.
19750
+ * If it is near, make the spot selection request.
19751
+ */
19752
+ this.intersectionObserver.observe(placement, spotPlacementIsNearCallback, {
19753
+ rootMargin: '500px',
19754
+ });
19755
+ }
19756
+ }
19757
+ /**
19758
+ * Injects a single spot element into the provided placement.
19759
+ *
19760
+ * @param {HTMLElement} placement - The placement element.
19761
+ * @param {ISpot} spot - The spot data.
19762
+ * @param {IInjectSpotElementConfig} config - The configuration object.
19763
+ *
19764
+ * @return {void}
19765
+ */
19766
+ injectOneSpotElement(placement, spot, config) {
19767
+ var _a;
19768
+ const placementId = placement.id;
19769
+ const spotType = spot.spot;
19770
+ this.eventService.handleSpotState(placementId, {
19771
+ identifier: {
19772
+ placementId,
19773
+ spotType,
19774
+ spotId: spot.id,
19775
+ },
19776
+ displayConfig: {
19777
+ isSingleItem: true,
19778
+ isCarousel: false,
19779
+ isCarouselItem: false,
19780
+ },
19781
+ });
19782
+ const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
19783
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19784
+ if (!content) {
19785
+ this.eventService.handleSpotState(placementId, {
19786
+ state: {
19787
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
19788
+ mounted: false,
19789
+ loading: false,
19790
+ },
19791
+ });
19792
+ clearPlacement(placementId);
19793
+ return;
19794
+ }
19795
+ // Create the spot element
19796
+ const spotElement = this.elementService.createSpotElement({
19797
+ content,
19798
+ config: {
19799
+ fluid: config === null || config === void 0 ? void 0 : config.fluid,
19800
+ spot: spot.spot,
19801
+ width: spot.width,
19802
+ height: spot.height,
19803
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
19804
+ },
19805
+ });
19806
+ if (!spotElement) {
19807
+ this.eventService.handleSpotState(placementId, {
19808
+ state: {
19809
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
19810
+ mounted: false,
19811
+ loading: false,
19812
+ },
19813
+ });
19814
+ clearPlacement(placementId);
19815
+ return;
19632
19816
  }
19817
+ this.eventService.registerSpot({
19818
+ spot: spotData,
19819
+ placementId,
19820
+ spotElement,
19821
+ });
19822
+ placement.replaceChildren(spotElement);
19823
+ this.eventService.handleSpotState(placementId, {
19824
+ dom: {
19825
+ spotElement,
19826
+ visibleOnViewport: false,
19827
+ },
19828
+ state: {
19829
+ mounted: true,
19830
+ loading: false,
19831
+ error: undefined,
19832
+ },
19833
+ });
19633
19834
  }
19634
- /** ========================= HELPER METHODS ========================= **/
19635
19835
  /**
19636
19836
  * Injects a carousel element with the provided spots into the placement.
19637
19837
  *
@@ -19643,11 +19843,12 @@ class LiquidCommerceRmnClient {
19643
19843
  */
19644
19844
  injectCarouselSpotElement(placement, spots, config) {
19645
19845
  var _a;
19846
+ const placementId = placement.id;
19646
19847
  const carouselSlides = [];
19647
19848
  for (const spotItem of spots) {
19648
- this.eventService.handleSpotState(placement.id, {
19849
+ this.eventService.handleSpotState(placementId, {
19649
19850
  identifier: {
19650
- placementId: placement.id,
19851
+ placementId,
19651
19852
  spotType: spotItem.spot,
19652
19853
  spotId: spotItem.id,
19653
19854
  },
@@ -19660,7 +19861,7 @@ class LiquidCommerceRmnClient {
19660
19861
  const spot = this.elementService.overrideSpotColors(spotItem, config === null || config === void 0 ? void 0 : config.colors);
19661
19862
  const content = SPOT_TEMPLATE_HTML_ELEMENT(spot, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19662
19863
  if (!content) {
19663
- this.eventService.handleSpotState(placement.id, {
19864
+ this.eventService.handleSpotState(placementId, {
19664
19865
  state: {
19665
19866
  error: `Failed to inject carousel spot item element. Could not create element for type "${spot.spot}".`,
19666
19867
  mounted: false,
@@ -19671,7 +19872,7 @@ class LiquidCommerceRmnClient {
19671
19872
  }
19672
19873
  this.eventService.registerSpot({
19673
19874
  spot,
19674
- placementId: placement.id,
19875
+ placementId,
19675
19876
  spotElement: content,
19676
19877
  });
19677
19878
  carouselSlides.push(content);
@@ -19693,18 +19894,18 @@ class LiquidCommerceRmnClient {
19693
19894
  },
19694
19895
  });
19695
19896
  if (!carouselElement) {
19696
- this.eventService.handleSpotState(placement.id, {
19897
+ this.eventService.handleSpotState(placementId, {
19697
19898
  state: {
19698
19899
  error: `Failed to inject spot carousel element. Could not create spot carousel element.`,
19699
19900
  mounted: false,
19700
19901
  loading: false,
19701
19902
  },
19702
19903
  });
19703
- this.clearPlacement(placement.id);
19904
+ clearPlacement(placementId);
19704
19905
  return;
19705
19906
  }
19706
19907
  placement.replaceChildren(carouselElement);
19707
- this.eventService.handleSpotState(placement.id, {
19908
+ this.eventService.handleSpotState(placementId, {
19708
19909
  dom: {
19709
19910
  spotElement: carouselElement,
19710
19911
  visibleOnViewport: false,
@@ -19716,94 +19917,6 @@ class LiquidCommerceRmnClient {
19716
19917
  },
19717
19918
  });
19718
19919
  }
19719
- /**
19720
- * Injects a single spot element into the provided placement.
19721
- *
19722
- * @param {IInjectSpotElement} injectItem - The inject item data.
19723
- * @param {HTMLElement} placement - The placement element.
19724
- * @param {ISpot} spot - The spot data.
19725
- * @param {IInjectSpotElementConfig} config - The configuration object.
19726
- *
19727
- * @return {void}
19728
- */
19729
- injectOneSpotElement(injectItem, placement, spot, config) {
19730
- var _a;
19731
- this.eventService.handleSpotState(injectItem.placementId, {
19732
- identifier: {
19733
- placementId: injectItem.placementId,
19734
- spotType: injectItem.spotType,
19735
- spotId: spot.id,
19736
- },
19737
- displayConfig: {
19738
- isSingleItem: true,
19739
- isCarousel: false,
19740
- isCarouselItem: false,
19741
- },
19742
- });
19743
- const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
19744
- const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19745
- if (!content) {
19746
- this.eventService.handleSpotState(injectItem.placementId, {
19747
- state: {
19748
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
19749
- mounted: false,
19750
- loading: false,
19751
- },
19752
- });
19753
- this.clearPlacement(injectItem.placementId);
19754
- return;
19755
- }
19756
- // Create the spot element
19757
- const spotElement = this.elementService.createSpotElement({
19758
- content,
19759
- config: {
19760
- fluid: config === null || config === void 0 ? void 0 : config.fluid,
19761
- spot: spot.spot,
19762
- width: spot.width,
19763
- height: spot.height,
19764
- minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
19765
- },
19766
- });
19767
- if (!spotElement) {
19768
- this.eventService.handleSpotState(injectItem.placementId, {
19769
- state: {
19770
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
19771
- mounted: false,
19772
- loading: false,
19773
- },
19774
- });
19775
- this.clearPlacement(injectItem.placementId);
19776
- return;
19777
- }
19778
- this.eventService.registerSpot({
19779
- spot: spotData,
19780
- placementId: injectItem.placementId,
19781
- spotElement,
19782
- });
19783
- placement.replaceChildren(spotElement);
19784
- this.eventService.handleSpotState(injectItem.placementId, {
19785
- dom: {
19786
- spotElement,
19787
- visibleOnViewport: false,
19788
- },
19789
- state: {
19790
- mounted: true,
19791
- loading: false,
19792
- error: undefined,
19793
- },
19794
- });
19795
- }
19796
- /**
19797
- * Clears the placement element by removing all its children.
19798
- *
19799
- * @param {string} placementId - The placement id.
19800
- *
19801
- * @return {void}
19802
- */
19803
- clearPlacement(placementId) {
19804
- var _a;
19805
- (_a = document.getElementById(placementId)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
19806
- }
19807
19920
  /**
19808
19921
  * Makes a selection request on our server based on the provided data.
19809
19922
  *
@@ -19811,77 +19924,23 @@ class LiquidCommerceRmnClient {
19811
19924
  *
19812
19925
  * @return {Promise<ISpots | {error: string}>} - The spots response object.
19813
19926
  */
19814
- async spotSelectionRequest(params) {
19927
+ async injectSpotSelectionRequest(params) {
19815
19928
  const { inject, filter, config } = params;
19929
+ const localStorageService = LocalStorageService.getInstance();
19930
+ const spots = inject.map((item) => ({
19931
+ placementId: item.placementId,
19932
+ spot: item.spotType,
19933
+ count: item === null || item === void 0 ? void 0 : item.count,
19934
+ ...item === null || item === void 0 ? void 0 : item.filter,
19935
+ }));
19816
19936
  const request = {
19937
+ userId: localStorageService.getUserId(),
19817
19938
  url: config === null || config === void 0 ? void 0 : config.url,
19818
19939
  filter,
19819
- spots: inject.map((item) => ({
19820
- placementId: item.placementId,
19821
- spot: item.spotType,
19822
- count: item === null || item === void 0 ? void 0 : item.count,
19823
- ...item === null || item === void 0 ? void 0 : item.filter,
19824
- })),
19940
+ spots,
19825
19941
  };
19826
19942
  return this.spotSelection(request);
19827
19943
  }
19828
- /**
19829
- * Prevents duplicate placement ids in the inject data.
19830
- *
19831
- * @param {IInjectSpotElement[]} inject - The inject data.
19832
- *
19833
- * @throws {Error} - If a duplicate placement id is found.
19834
- *
19835
- * @return {void}
19836
- */
19837
- preventDuplicateSpotPlacementIds(inject) {
19838
- const placementIds = new Set();
19839
- for (const item of inject) {
19840
- if (placementIds.has(item.placementId)) {
19841
- this.eventService.handleSpotState(item.placementId, {
19842
- state: {
19843
- error: `Duplicate placement id (${item.placementId}) found. Please provide a unique placement id for each spot element.`,
19844
- },
19845
- });
19846
- return false;
19847
- }
19848
- placementIds.add(item.placementId);
19849
- }
19850
- return true;
19851
- }
19852
- /**
19853
- * Prevents non-existent spot types in the inject data.
19854
- *
19855
- * @param {IInjectSpotElement[]} inject - The inject data.
19856
- * @return {IInjectSpotElement[]} - The filtered inject data.
19857
- */
19858
- preventNonExistentSpotTypes(inject) {
19859
- const newInject = [];
19860
- for (const item of inject) {
19861
- if (!Object.values(RMN_SPOT_TYPE).includes(item.spotType)) {
19862
- this.eventService.handleSpotState(item.placementId, {
19863
- state: {
19864
- error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
19865
- },
19866
- });
19867
- continue;
19868
- }
19869
- newInject.push(item);
19870
- }
19871
- return newInject;
19872
- }
19873
- // Use spot selection example data for private testing
19874
- useSpotSelectionExample(inject) {
19875
- const examples = { ...RB_SPOTS_SELECTION_EXAMPLE, ...IAB_SPOTS_SELECTION_EXAMPLE };
19876
- const data = {};
19877
- inject.map((item) => {
19878
- var _a, _b, _c;
19879
- data[item.placementId] = (_c = (_a = examples[item.spotType]) === null || _a === void 0 ? void 0 : _a.slice(0, (_b = item.count) !== null && _b !== void 0 ? _b : 1)) !== null && _c !== void 0 ? _c : [];
19880
- });
19881
- return new Promise((resolve) => {
19882
- resolve(data);
19883
- });
19884
- }
19885
19944
  }
19886
19945
  /**
19887
19946
  * Creates a new instance of the RmnClient.