1import { 2 formatCurrencyValue, 3 getCurrencyFromRoot, 4 getDomainInfoState, 5 getRootData, 6 tText, 7} from '../../client-utils.js'; 8import { normalizeText } from '../../value-utils.js'; 9import { PRODUCT_IDS } from '../../product-ids.js'; 10import { normalizePlanKey, getPlanLabel, toNumber } from '../domain/plans-shared.js'; 11import { toDisplayDomainOrderPeriod } from '../domain/domain-pricing-options.js'; 12import { getCartProductDefinition, isPlanCartProduct } from './plan-cart-model.js'; 13import { getDomainTld } from './normalization.js'; 14import { getCartDomainName } from './cart-domain-names.js'; 15 16const formatCartCurrency = (value) => 17 formatCurrencyValue(toNumber(value), { 18 rootData: getRootData(), 19 }); 20 21const formatCartPeriod = (period) => { 22 const parsed = Number(period); 23 const safe = Number.isFinite(parsed) && parsed > 0 ? parsed : 1; 24 const key = safe === 1 ? 'backend.common.cartForm.orderPeriod.year' : 'backend.common.cartForm.orderPeriod.years'; 25 return tText(key, { vars: { number: { $val: safe } } }); 26}; 27 28const formatCartDomainSubtitle = (periodLabel) => 29 `${periodLabel}, ${tText('backend.common.domainNamePlans.domainOnly')}`; 30 31const getDomainProductType = (productName) => { 32 const types = { 33 'Holder change': 'backend.common.cartForm.domainType.holderchange', 34 Registration: 'backend.common.cartForm.domainType.registration', 35 Transfer: 'backend.common.cartForm.domainType.transfer', 36 Renewal: 'backend.common.cartForm.domainType.renewal', 37 Restore: 'backend.common.cartForm.domainType.restore', 38 }; 39 return tText(types[productName] || types.Registration); 40}; 41 42const getPlanProductType = (productName) => { 43 const types = { 44 'Renew all in one plan': 'backend.common.cartForm.domainType.renewal', 45 'Upgrade all in one plan': 'backend.common.cart.upgradePlan', 46 }; 47 return tText(types[productName] || 'backend.common.cartForm.domainType.newPlan'); 48}; 49 50const formatCartPlanId = (planId) => 51 tText('backend.common.cartForm.plan.id', { 52 vars: { id: { $val: planId } }, 53 }); 54 55const getDomainAction = (productName) => { 56 const actions = { 57 'Holder change': 'holder-change', 58 Transfer: 'transfer', 59 Renewal: 'renewal', 60 Restore: 'restore', 61 }; 62 return actions[productName] || 'registration'; 63}; 64 65const resolveCartDomainName = (product) => { 66 const configuration = product?.shopping_cart_product_configuration?.product_configuration || null; 67 const directName = getCartDomainName(product); 68 if (directName) { 69 return directName; 70 } 71 72 const referencedDomainId = Number( 73 configuration?.domain_name_to_renew_id ?? configuration?.domain_name_to_restore_id ?? configuration?.domain_name_id, 74 ); 75 if (!Number.isFinite(referencedDomainId) || referencedDomainId <= 0) { 76 return ''; 77 } 78 79 const name = normalizeText(configuration?.name); 80 const topLevelDomain = normalizeText(configuration?.top_level_domain); 81 if (name && topLevelDomain) { 82 return `${name}.${topLevelDomain}`; 83 } 84 85 const domainInfo = getDomainInfoState()?.data || {}; 86 const referencedDomain = [...(domainInfo.activeDomains || []), ...(domainInfo.expiredDomains || [])].find( 87 (domain) => Number(domain?.id) === referencedDomainId, 88 ); 89 return normalizeText(referencedDomain?.name); 90}; 91 92const resolveCartDisplayDomainName = (product) => 93 normalizeText(product?.display_domain_name) || resolveCartDomainName(product); 94 95const createCartEventItem = (product, index) => { 96 const currency = getCurrencyFromRoot(getRootData()) || 'EUR'; 97 const configuration = product?.shopping_cart_product_configuration?.product_configuration || null; 98 const productType = normalizeText(product?.product?.type?.type).toLowerCase(); 99 let name = ''; 100 101 if (productType === 'domain') { 102 name = normalizeText(resolveCartDisplayDomainName(product)).toLowerCase(); 103 } else if (productType === 'plan') { 104 name = normalizeText(configuration?.plan_name).toLowerCase(); 105 } else { 106 name = normalizeText(configuration?.product?.name?.name || product?.product?.name?.name).toLowerCase(); 107 } 108 109 return { 110 price: product?.price, 111 name, 112 index, 113 currency, 114 }; 115}; 116 117const buildCartProductViewModel = (product) => { 118 const isDomain = product?.product?.type?.type === 'domain'; 119 const configuration = product?.shopping_cart_product_configuration?.product_configuration || null; 120 const orderPeriod = configuration?.order_period; 121 const displayOrderPeriod = 122 isDomain && orderPeriod != null 123 ? toDisplayDomainOrderPeriod(orderPeriod, configuration?.minimum_year) 124 : orderPeriod; 125 const periodLabel = formatCartPeriod(displayOrderPeriod); 126 const productId = product?.id != null ? String(product.id) : ''; 127 const productName = product?.product?.name?.name; 128 129 if (isDomain) { 130 const isHolderChange = productName === 'Holder change';
131 const domainName = resolveCartDomainName(product); 132 const configId = Number(product?.shopping_cart_product_configuration?.id); 133 const hasConfig = Number.isFinite(configId) && configId > 0; 134 const domainType = getDomainProductType(productName); 135 136 return { 137 productId, 138 title: resolveCartDisplayDomainName(product) || domainType, 139 subtitle: isHolderChange ? '' : formatCartDomainSubtitle(periodLabel), 140 type: domainType, 141 price: formatCartCurrency(product?.price), 142 config: { 143 type: 'domain', 144 id: hasConfig ? String(configId) : '', 145 domainName, 146 domainTld: getDomainTld(domainName), 147 orderPeriod: orderPeriod != null ? String(displayOrderPeriod) : '', 148 privacyProtection: Number(configuration?.privacy_protection) === 1 ? '1' : '0', 149 domainAction: getDomainAction(productName), 150 transferKey: normalizeText(configuration?.auth_key), 151 domainHolderContactId: normalizeText( 152 isHolderChange ? configuration?.new_contact_id : configuration?.contact?.id, 153 ), 154 ...(isHolderChange 155 ? { 156 domainId: normalizeText(configuration?.domain_name_id), 157 allowUserChange: Number(configuration?.allow_user_change) ? '1' : '0', 158 } 159 : {}), 160 hasConfig, 161 }, 162 }; 163 } 164 165 const planName = configuration?.plan_name || ''; 166 const planKey = normalizePlanKey(planName || product?.product?.name?.name || ''); 167 const configId = Number(product?.shopping_cart_product_configuration?.id); 168 const hasConfig = Number.isFinite(configId) && configId > 0; 169 const isUpgrade = getCartProductDefinition(product)?.id === PRODUCT_IDS.UPGRADE_ALL_IN_ONE_PLAN; 170 const upgradedPlanId = configuration?.all_in_one_plan?.id ?? configuration?.customer_plan?.id; 171 172 return { 173 productId, 174 title: getPlanLabel(planKey || planName || product?.product?.name?.name || '') || planName, 175 subtitle: isUpgrade && upgradedPlanId != null ? formatCartPlanId(upgradedPlanId) : periodLabel, 176 type: getPlanProductType(productName), 177 price: formatCartCurrency(product?.price), 178 config: 179 isUpgrade || !isPlanCartProduct(product) 180 ? null 181 : { 182 type: 'plan', 183 id: hasConfig ? String(configId) : '', 184 planKey, 185 planActionId: getCartProductDefinition(product)?.id || '', 186 orderPeriod: orderPeriod != null ? String(orderPeriod) : '', 187 hasConfig, 188 }, 189 }; 190}; 191 192const renderCartProductList = ({ 193 container, 194 template, 195 products, 196 allowConfig = true, 197 allowRemove = true, 198 isConfigAvailable = false, 199} = {}) => { 200 if (!(container instanceof HTMLElement) || !(template instanceof HTMLTemplateElement)) { 201 return; 202 } 203 204 container.replaceChildren(); 205 206 if (!Array.isArray(products) || !products.length) { 207 return; 208 } 209 210 products.forEach((product) => { 211 const fragment = template.content.cloneNode(true); 212 const item = fragment.querySelector('[data-cart-item]'); 213 const title = fragment.querySelector('[data-cart-item-title]'); 214 const subtitle = fragment.querySelector('[data-cart-item-subtitle]'); 215 const type = fragment.querySelector('[data-cart-item-type]'); 216 const price = fragment.querySelector('[data-cart-item-price]'); 217 const removeButton = fragment.querySelector('[data-cart-item-remove]'); 218 const configButton = fragment.querySelector('[data-cart-item-config]'); 219 const configLabel = fragment.querySelector('[data-cart-item-config-label]'); 220 const viewModel = buildCartProductViewModel(product); 221 222 if (title instanceof HTMLElement) { 223 title.textContent = viewModel.title; 224 } 225 if (subtitle instanceof HTMLElement) { 226 subtitle.textContent = viewModel.subtitle; 227 if (viewModel.subtitle) { 228 subtitle.removeAttribute('hidden'); 229 } else { 230 subtitle.setAttribute('hidden', ''); 231 } 232 } 233 if (type instanceof HTMLElement) { 234 type.textContent = viewModel.type; 235 } 236 if (price instanceof HTMLElement) { 237 price.textContent = viewModel.price; 238 } 239 if (configLabel instanceof HTMLElement) { 240 configLabel.textContent = tText( 241 viewModel.config?.type === 'domain' 242 ? 'backend.common.cartForm.headings.domainConfiguration' 243 : 'backend.common.cartForm.headings.planConfiguration', 244 ); 245 } 246 247 if (item instanceof HTMLElement) { 248 item.setAttribute('data-cart-item-id', viewModel.productId); 249 } 250 251 if (removeButton instanceof HTMLElement) { 252 removeButton.setAttribute('data-cart-item-id', viewModel.productId); 253 removeButton.setAttribute('aria-label', tText('common.cart.removeProduct')); 254 if (!allowRemove) { 255 removeButton.setAttribute('hidden', ''); 256 if (removeButton instanceof HTMLButtonElement) { 257 removeButton.disabled = true; 258 removeButton.setAttribute('aria-disabled', 'true'); 259 } 260 } else { 261 removeButton.removeAttribute('hidden'); 262 if (removeButton instanceof HTMLButtonElement) { 263 removeButton.disabled = false;
264 removeButton.removeAttribute('aria-disabled'); 265 } 266 } 267 } 268 269 if (configButton instanceof HTMLElement) { 270 const showConfig = allowConfig && isConfigAvailable && Boolean(viewModel.config); 271 const syncConfigAttribute = (name, value) => { 272 if (value !== undefined && value !== null && value !== '') { 273 configButton.setAttribute(name, value); 274 return; 275 } 276 configButton.removeAttribute(name); 277 }; 278 279 if (!viewModel.config) { 280 configButton.remove(); 281 } else if (!showConfig) { 282 configButton.setAttribute('hidden', ''); 283 if (configButton instanceof HTMLButtonElement) { 284 configButton.disabled = true; 285 configButton.setAttribute('aria-disabled', 'true'); 286 } 287 } else { 288 configButton.removeAttribute('hidden'); 289 290 syncConfigAttribute('data-cart-config-id', viewModel.config.id); 291 syncConfigAttribute('data-cart-config-type', viewModel.config.type); 292 syncConfigAttribute('data-cart-config-plan', viewModel.config.planKey); 293 syncConfigAttribute('data-cart-config-plan-action-id', viewModel.config.planActionId); 294 syncConfigAttribute('data-cart-config-period', viewModel.config.orderPeriod); 295 syncConfigAttribute('data-cart-config-domain', viewModel.config.domainName); 296 syncConfigAttribute('data-cart-config-tld', viewModel.config.domainTld); 297 syncConfigAttribute('data-cart-config-privacy-protection', viewModel.config.privacyProtection); 298 syncConfigAttribute('data-cart-config-domain-action', viewModel.config.domainAction); 299 syncConfigAttribute('data-cart-config-transfer-key', viewModel.config.transferKey); 300 syncConfigAttribute('data-cart-config-domain-holder-contact', viewModel.config.domainHolderContactId); 301 syncConfigAttribute('data-cart-config-domain-id', viewModel.config.domainId); 302 syncConfigAttribute('data-cart-config-allow-user-change', viewModel.config.allowUserChange); 303 304 if (configButton instanceof HTMLButtonElement) { 305 configButton.disabled = !viewModel.config.hasConfig; 306 if (viewModel.config.hasConfig) { 307 configButton.removeAttribute('aria-disabled'); 308 } else { 309 configButton.setAttribute('aria-disabled', 'true'); 310 } 311 } 312 } 313 } 314 315 container.appendChild(fragment); 316 }); 317}; 318 319export { 320 buildCartProductViewModel, 321 createCartEventItem, 322 formatCartCurrency, 323 formatCartPeriod, 324 renderCartProductList, 325 resolveCartDomainName, 326};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.