An error occurred while processing the template.
The following has evaluated to null or missing:
==> dateHelper [in template "86196622878935#20120#46912" at line 6, column 24]
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign expDate = dateHelper.getForma... [in template "86196622878935#20120#46912" at line 6, column 5]
----
1<#assign journalArticleLocalService = serviceLocator.findService("com.liferay.journal.service.JournalArticleLocalService") />
2<#assign articleId = .vars["reserved-article-id"].data />
3
4<#if journalArticleLocalService??>
5 <#assign article = journalArticleLocalService.fetchLatestArticle(themeDisplay.getScopeGroupId(), articleId,0) />
6 <#assign expDate = dateHelper.getFormattedDate(article.getExpirationDate(), "d MMM", locale)/>
7 <#if expDate?has_content>
8 <#assign expiryLabel = languageUtil.format(locale, "stc.offers.label.offer-expiry", expDate) />
9 <#else>
10 <#assign expiryLabel = languageUtil.get(locale, "stc.offers.label.offer-expiry-never-expires") />
11 </#if>
12</#if>
13
14<section class="offer-detailed container">
15 <div class="offer-detailed__left-column">
16 <div class="offer-detailed__top">
17 <div class="offer-detailed__top-container">
18 <#if (title.getData())?? && title.getData() != "">
19 <h1 class="offer-detailed__title">${title.getData()}</h1>
20 </#if>
21 <#if (shortDescription.getData())?? && shortDescription.getData() != "">
22 <p class="offer-detailed__description">${shortDescription.getData()}</p>
23 </#if>
24 </div>
25
26 <#if (promotionCode.getData())?? && promotionCode.getData()!= "">
27 <div class="offer-detailed__promo-code-container">
28 <p class="offer-detailed__promo-code">
29 ${promotionCode.getData()}
30 </p>
31 <span class="stc-icon-copy" role="button" tabindex="0"
32 aria-label="Copy promotion code"></span>
33 </div>
34 </#if>
35
36 <#if (image.getData())?? && image.getData() != "">
37 <div class="offer-detailed__image-container--small-screen">
38 <div class="offer-detailed__image">
39 <img alt="${image.getAttribute("alt")}"
40 data-fileentryid="${image.getAttribute("fileEntryId")}"
41 src="${image.getData()}"/>
42 <div class="offer-detailed__expiry">
43 ${expiryLabel}
44 </div>
45 </div>
46 </div>
47 </#if>
48
49 </div>
50 <div class="offer-detailed__bottom">
51 <#if (validCards.getSiblings()?first.validCardImg.getData())?has_content>
52
53 <#if (validLabel.getData())?? &&validLabel.getData() !="">
54 <h3 class="offer-detailed__valid-label">
55 ${validLabel.getData()}</h3>
56 </#if>
57
58 <div class="offer-detailed__images-valid-cards">
59 <#list validCards.getSiblings() as validCard>
60 <#if (validCard.validCardImg.getData())??>
61 <img class="offer-detailed__images-valid-cards__image"
62 alt="${validCard.validCardImg.getAttribute("alt")}"
63 data-fileentryid="${validCard.validCardImg.getAttribute("fileEntryId")}"
64 src="${validCard.validCardImg.getData()}"/>
65
66 <#if (validCard.validCardHint.getData())??>
67 <div class="hint">${validCard.validCardHint.getData()}</div>
68 </#if>
69
70 </#if>
71 </#list>
72 </div>
73 </#if>
74
75 <#if (termsAndConditionsSectionTitle.getData())?? && termsAndConditionsSectionTitle.getData()!="">
76 <h3 class="offer-detailed__terms-title">${termsAndConditionsSectionTitle.getData()}</h3>
77 </#if>
78 <#if (termsAndConditionsRow.getData())?? && termsAndConditionsRow.getData()!="">
79 <div class="offer-detailed__terms-description">
80 ${termsAndConditionsRow.getData()}
81 </div>
82 </#if>
83
84 </div>
85 </div>
86 <div class="offer-detailed__right-column">
87 <#if (image.getData())?? && image.getData() != "">
88 <div class="offer-detailed__image-container">
89 <div class="offer-detailed__image">
90 <img alt="${image.getAttribute("alt")}"
91 data-fileentryid="${image.getAttribute("fileEntryId")}"
92 src="${image.getData()}"/>
93 <div class="offer-detailed__expiry">
94 ${expiryLabel}
95 </div>
96 </div>
97 </div>
98 </#if>
99 </div>
100
101 <!-- Popup -->
102 <#if (promoCodeCopiedlabel.getData())?? && promoCodeCopiedlabel.getData()!="">
103 <div class="promo-popup" id="promoPopup" role="alert" aria-live="assertive">
104 <div class="promo-popup__content">
105 <span class="promo-popup__done stc-icon-done-24" aria-hidden="true"></span>
106 <p class="popup-text">${promoCodeCopiedlabel.getData()}</p>
107 </div>
108 <a class="stc-icon-cross-16 promo-popup__close" id="popupClose" aria-label="Close popup"
109 tabindex="0"></a>
110 </div>
111 </#if>
112
113</section>
114
115<script>
116 //Cards hover
117 const pictures = document.querySelectorAll('.offer-detailed__images-valid-cards > picture');
118 pictures.forEach((pic) => {
119 pic.setAttribute('tabindex', '0');
120 });
121
122 if (pictures.length > 0) {
123 const isRTL = document.documentElement.dir === 'rtl';
124
125 function showHint(pic) {
126 const hint = pic.nextElementSibling;
127 if (pic && hint) {
128 const containerRect = pic.parentElement.getBoundingClientRect();
129 const picRect = pic.getBoundingClientRect();
130
131 if (containerRect && picRect) {
132 const offset = (picRect.width / 2);
133 hint.style.position = 'absolute';
134 hint.style.top = (picRect.bottom - containerRect.top + 12) + 'px';
135 hint.style.opacity = '1';
136 hint.style.visibility = 'visible';
137
138 if (isRTL) {
139 const rightOffset = (containerRect.right - picRect.right) + offset;
140 hint.style.right = rightOffset + 'px';
141 hint.style.left = 'auto'; // fontos!
142 } else {
143 const leftOffset = (picRect.left - containerRect.left) + offset;
144 hint.style.left = leftOffset + 'px';
145 hint.style.right = 'auto'; // fontos!
146 }
147 }
148 }
149 }
150
151 function hideHint(pic) {
152 const hint = pic.nextElementSibling;
153 if (hint) {
154 hint.style.opacity = '0';
155 hint.style.visibility = 'hidden';
156 }
157 }
158
159 pictures.forEach(pic => {
160 ['mouseenter', 'focus'].forEach(eventType => {
161 pic.addEventListener(eventType, () => showHint(pic));
162 });
163 ['mouseleave', 'blur'].forEach(eventType => {
164 pic.addEventListener(eventType, () => hideHint(pic));
165 });
166 });
167 }
168
169
170 // Copy promo code
171 function copyToClipboard(promoCode) {
172 navigator.clipboard.writeText(promoCode).then(() => {
173 }).catch((error) => {
174 console.error("Failed to copy promo code: ", error);
175 });
176 }
177
178 function showPopup() {
179 const popup = document.getElementById('promoPopup');
180 if (popup) {
181 popup.classList.add('show');
182 setTimeout(closePopup, 2500);
183 }
184 }
185
186 function closePopup() {
187 const popup = document.getElementById('promoPopup');
188 if (popup) {
189 popup.classList.remove('show');
190 }
191 }
192
193 function handleCopyClick() {
194 const promoCodeElement = document.querySelector('.offer-detailed__promo-code');
195 if (promoCodeElement) {
196 const promoCode = promoCodeElement.textContent.trim();
197 copyToClipboard(promoCode);
198 showPopup();
199
200 const closeButton = document.getElementById('popupClose');
201 if (closeButton) {
202 closeButton.addEventListener('click', closePopup);
203 closeButton.addEventListener('keydown', (event) => {
204 if (event.key === 'Enter' || event.key === ' ') {
205 event.preventDefault();
206 closePopup();
207 }
208 });
209 }
210 }
211 }
212
213 const copyIcon = document.querySelector('.stc-icon-copy');
214 if (copyIcon) {
215 copyIcon.addEventListener('click', handleCopyClick);
216 copyIcon.addEventListener('keydown', (event) => {
217 if (event.key === 'Enter' || event.key === ' ') {
218 event.preventDefault();
219 handleCopyClick();
220 }
221 });
222 }
223</script>