From e7608ccdf635a2a59784dc77758d8314062b519a Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 15:06:27 +0530 Subject: [PATCH 01/17] adding logo cards block --- blocks/logo-cards/_logo-cards.json | 82 ++++++++++++++++++++++++++++++ blocks/logo-cards/logo-cards.css | 67 ++++++++++++++++++++++++ blocks/logo-cards/logo-cards.js | 74 +++++++++++++++++++++++++++ component-definition.json | 30 +++++++++++ component-filters.json | 7 +++ component-models.json | 37 ++++++++++++++ 6 files changed, 297 insertions(+) create mode 100644 blocks/logo-cards/_logo-cards.json create mode 100644 blocks/logo-cards/logo-cards.css create mode 100644 blocks/logo-cards/logo-cards.js diff --git a/blocks/logo-cards/_logo-cards.json b/blocks/logo-cards/_logo-cards.json new file mode 100644 index 0000000..588596e --- /dev/null +++ b/blocks/logo-cards/_logo-cards.json @@ -0,0 +1,82 @@ +{ + "definitions": [ + { + "title": "Logo Cards", + "id": "logo-cards", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Logo Cards", + "filter": "logo-cards" + } + } + } + } + }, + { + "title": "Logo Card", + "id": "logo-card", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block/item", + "template": { + "name": "Logo Card", + "model": "logo-card" + } + } + } + } + } + ], + "models": [ + { + "id": "logo-card", + "fields": [ + { + "component": "reference", + "valueType": "string", + "name": "icon", + "label": "Logo / Icon", + "multi": false + }, + { + "component": "text", + "valueType": "string", + "name": "title", + "label": "Title" + }, + { + "component": "richtext", + "valueType": "string", + "name": "description", + "label": "Description", + "value": "" + }, + { + "component": "text", + "valueType": "string", + "name": "cta-label", + "label": "CTA Label" + }, + { + "component": "text", + "valueType": "string", + "name": "cta-url", + "label": "CTA URL" + } + ] + } + ], + "filters": [ + { + "id": "logo-cards", + "components": [ + "logo-card" + ] + } + ] +} + diff --git a/blocks/logo-cards/logo-cards.css b/blocks/logo-cards/logo-cards.css new file mode 100644 index 0000000..56ce178 --- /dev/null +++ b/blocks/logo-cards/logo-cards.css @@ -0,0 +1,67 @@ +.logo-cards > ul { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 24px; +} + +.logo-cards > ul > li { + display: flex; + align-items: flex-start; + gap: 16px; + padding: 20px 24px; + border: 1px solid #10b4a2; + background-color: var(--background-color, #fff); +} + +.logo-card-icon { + flex: 0 0 auto; + line-height: 0; +} + +.logo-card-icon picture, +.logo-card-icon img { + display: block; + width: 40px; + height: 40px; + object-fit: contain; +} + +.logo-card-content { + flex: 1 1 auto; +} + +.logo-card-title { + margin: 0 0 4px; + font-size: 1rem; + font-weight: 600; +} + +.logo-card-description { + margin: 0; + font-size: 0.9rem; + color: var(--text-color, #444); +} + +.logo-card-cta { + display: inline-block; + margin-top: 8px; + font-size: 0.9rem; + font-weight: 600; + color: var(--link-color, #10b4a2); + text-decoration: none; +} + +.logo-card-cta:hover, +.logo-card-cta:focus { + text-decoration: underline; +} + +@media (max-width: 600px) { + .logo-cards > ul > li { + padding: 16px 18px; + } +} + diff --git a/blocks/logo-cards/logo-cards.js b/blocks/logo-cards/logo-cards.js new file mode 100644 index 0000000..590b916 --- /dev/null +++ b/blocks/logo-cards/logo-cards.js @@ -0,0 +1,74 @@ +import { createOptimizedPicture } from '../../scripts/aem.js'; + +export default function decorate(block) { + const rows = [...block.querySelectorAll(':scope > div')]; + + const ul = document.createElement('ul'); + + rows.forEach((row) => { + const li = document.createElement('li'); + const cells = [...row.children]; + + const iconCell = cells[0] || null; + const titleCell = cells[1] || null; + const descriptionCell = cells[2] || null; + const ctaCell = cells[3] || null; + + let iconWrapper = null; + if (iconCell) { + const rawImg = iconCell.querySelector('picture > img, img'); + if (rawImg && rawImg.src) { + const optimized = createOptimizedPicture( + rawImg.src, + rawImg.alt || '', + false, + [{ width: '64' }], + ); + iconWrapper = document.createElement('div'); + iconWrapper.className = 'logo-card-icon'; + iconWrapper.append(optimized); + } + } + + const content = document.createElement('div'); + content.className = 'logo-card-content'; + + const titleText = titleCell ? titleCell.textContent.trim() : ''; + if (titleText) { + const titleEl = document.createElement('h3'); + titleEl.className = 'logo-card-title'; + titleEl.textContent = titleText; + content.appendChild(titleEl); + } + + const descriptionText = descriptionCell ? descriptionCell.textContent.trim() : ''; + if (descriptionText) { + const descEl = document.createElement('p'); + descEl.className = 'logo-card-description'; + descEl.textContent = descriptionText; + content.appendChild(descEl); + } + + if (ctaCell) { + const link = ctaCell.querySelector('a'); + if (link && link.href) { + const ctaEl = document.createElement('a'); + ctaEl.className = 'logo-card-cta'; + ctaEl.href = link.href; + ctaEl.textContent = (link.textContent || '').trim() || link.href; + content.appendChild(ctaEl); + } + } + + if (iconWrapper) { + li.appendChild(iconWrapper); + } + + li.appendChild(content); + ul.appendChild(li); + }); + + block.innerHTML = ''; + block.appendChild(ul); +} + diff --git a/component-definition.json b/component-definition.json index 0ee8f70..1effcf1 100644 --- a/component-definition.json +++ b/component-definition.json @@ -97,6 +97,36 @@ } } }, + { + "title": "Logo Cards", + "id": "logo-cards", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Logo Cards", + "filter": "logo-cards" + } + } + } + } + }, + { + "title": "Logo Card", + "id": "logo-card", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block/item", + "template": { + "name": "Logo Card", + "model": "logo-card" + } + } + } + } + }, { "title": "Card", "id": "card", diff --git a/component-filters.json b/component-filters.json index ca1c484..d125437 100644 --- a/component-filters.json +++ b/component-filters.json @@ -14,6 +14,7 @@ "title", "hero", "cards", + "logo-cards", "columns", "fragment", "weather", @@ -31,6 +32,12 @@ "card" ] }, + { + "id": "logo-cards", + "components": [ + "logo-card" + ] + }, { "id": "columns", "components": [ diff --git a/component-models.json b/component-models.json index 06f5a87..65222e2 100644 --- a/component-models.json +++ b/component-models.json @@ -400,5 +400,42 @@ { "id": "carousel", "fields": [] + }, + { + "id": "logo-card", + "fields": [ + { + "component": "reference", + "valueType": "string", + "name": "icon", + "label": "Logo / Icon", + "multi": false + }, + { + "component": "text", + "valueType": "string", + "name": "title", + "label": "Title" + }, + { + "component": "richtext", + "valueType": "string", + "name": "description", + "label": "Description", + "value": "" + }, + { + "component": "text", + "valueType": "string", + "name": "cta-label", + "label": "CTA Label" + }, + { + "component": "text", + "valueType": "string", + "name": "cta-url", + "label": "CTA URL" + } + ] } ] \ No newline at end of file From d6cefcc4f6d321f80062b38f0095b080b5cec91e Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 15:08:29 +0530 Subject: [PATCH 02/17] adding logo cards block --- blocks/logo-cards/logo-cards.js | 1 - 1 file changed, 1 deletion(-) diff --git a/blocks/logo-cards/logo-cards.js b/blocks/logo-cards/logo-cards.js index 590b916..53325d6 100644 --- a/blocks/logo-cards/logo-cards.js +++ b/blocks/logo-cards/logo-cards.js @@ -71,4 +71,3 @@ export default function decorate(block) { block.innerHTML = ''; block.appendChild(ul); } - From 1864ab98b6a7de057957ad1e15f0b1d2cc6a9dfb Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 16:08:11 +0530 Subject: [PATCH 03/17] adding caraousal block --- blocks/carousel/_carousel.json | 104 +++++++++++++ blocks/carousel/carousel.css | 261 +++++++++++++++++++++++++++++++++ blocks/carousel/carousel.js | 154 +++++++++++++++++++ blocks/header/header.css | 4 +- component-definition.json | 17 ++- component-filters.json | 6 + component-models.json | 59 ++++++++ 7 files changed, 602 insertions(+), 3 deletions(-) create mode 100644 blocks/carousel/_carousel.json create mode 100644 blocks/carousel/carousel.css create mode 100644 blocks/carousel/carousel.js diff --git a/blocks/carousel/_carousel.json b/blocks/carousel/_carousel.json new file mode 100644 index 0000000..db15056 --- /dev/null +++ b/blocks/carousel/_carousel.json @@ -0,0 +1,104 @@ +{ + "definitions": [ + { + "title": "Carousel", + "id": "carousel", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Carousel", + "filter": "carousel" + } + } + } + } + }, + { + "title": "Carousel Card", + "id": "carousel-cards", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block/item", + "template": { + "name": "Carousel Cards", + "model": "carousel-cards" + } + } + } + } + } + ], + "models": [ + { + "id": "carousel", + "fields": [] + }, + { + "id": "carousel-cards", + "fields": [ + { + "component": "reference", + "valueType": "string", + "name": "image", + "label": "Image" + }, + { + "component": "select", + "valueType": "string", + "name": "alignment", + "label": "Card Alignment", + "value": "center", + "options": [ + { + "name": "Left", + "value": "left" + }, + { + "name": "Center", + "value": "center" + }, + { + "name": "Right", + "value": "right" + } + ] + }, + { + "component": "text", + "valueType": "string", + "name": "title", + "label": "Card Title" + }, + { + "component": "richtext", + "valueType": "string", + "name": "description", + "label": "Card Description" + }, + { + "component": "text", + "valueType": "string", + "name": "buttonLabel", + "label": "Button Label" + }, + { + "component": "aem-content", + "name": "buttonLink", + "label": "Button Link" + } + ] + } + ], + "filters": [ + { + "id": "carousel", + "components": [ + "carousel-cards" + ] + } + ] +} + diff --git a/blocks/carousel/carousel.css b/blocks/carousel/carousel.css new file mode 100644 index 0000000..73af487 --- /dev/null +++ b/blocks/carousel/carousel.css @@ -0,0 +1,261 @@ +/* ========================= + Base Carousel Layout +========================= */ + +.carousel { + position: relative; + overflow: hidden; +} + +.carousel-viewport { + overflow: hidden; + width: 100%; +} + +.carousel-track { + display: flex; + transition: transform 0.6s ease; +} + +.carousel-slide { + min-width: 100%; + position: relative; +} + +/* Image */ +.carousel-slide picture img { + width: 100%; + height: 500px; + object-fit: cover; + display: block; +} + +/* ========================= + Overlay Base (Auto Height) +========================= */ + +.carousel-overlay { + position: absolute; + bottom: 40px; + padding: 2.5rem 3rem; + width: 45%; + color: #fff; + z-index: 2; + border-radius: 6px; + background: rgba(0, 0, 0, 0.65); +} + +/* Gradient background for readability */ +.carousel-overlay::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: linear-gradient( + to right, + rgba(0, 0, 0, 0.75), + rgba(0, 0, 0, 0.4), + transparent + ); +} + +/* ========================= + Alignment Variants +========================= */ + +/* LEFT */ +.overlay-left { + left: 60px; + text-align: left; +} + +/* RIGHT */ +.overlay-right { + right: 60px; + text-align: left; +} + +/* CENTER */ +.overlay-center { + left: 50%; + transform: translateX(-50%); + text-align: center; + width: 60%; +} + +.overlay-right::before { + background: linear-gradient( + to left, + rgba(0, 0, 0, 0.75), + rgba(0, 0, 0, 0.4), + transparent + ); +} + +.overlay-center::before { + background: rgba(0, 0, 0, -0.45); +} + +/* ========================= + Overlay cells (preserve UE bindings) +========================= */ + +/* Hidden cells: keep in DOM for UE, hide visually */ +.carousel-overlay .carousel-alignment, +.carousel-overlay .carousel-button-style, +.carousel-overlay .carousel-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.carousel-overlay .carousel-title { + font-size: 2.5rem; + margin-bottom: 1rem; + line-height: 1.2; + font-weight: 700; +} + +.carousel-overlay .carousel-desc { + font-size: 1.1rem; + margin: 0.5rem 0; +} + +/* ========================= + Typography +========================= */ + +.carousel-overlay h1, +.carousel-overlay h2, +.carousel-overlay h3 { + font-size: 2.5rem; + margin-bottom: 1rem; + line-height: 1.2; +} + +.carousel-overlay p { + font-size: 1.1rem; + margin: 0.5rem 0; +} + +/* ========================= + Button Styling +========================= */ + +/* Higher specificity than styles.css a.button:any-link (0,2,1) */ +.carousel .carousel-overlay .carousel-button { + display: inline-block; + margin-top: 1.8rem; + padding: 0.85rem 2rem; + background-color: #C7C6C1; + color: #000 !important; + text-decoration: none !important; + font-weight: 600; + border-radius: 4px; + transition: all 0.3s ease; +} + +.carousel .carousel-overlay .carousel-button:hover { + background-color: #000; + color: #fff !important; + text-decoration: none !important; +} + +.carousel-overlay a { + text-decoration: none; +} + +/* ========================= + Navigation Arrows +========================= */ + +.carousel-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.6); + border: none; + color: #fff; + padding: 1rem 1.2rem; + cursor: pointer; + z-index: 3; + font-size: 1.2rem; + border-radius: 4px; + transition: 0.3s ease; +} + +.carousel-nav.prev { + left: 20px; +} + +.carousel-nav.next { + right: 20px; +} + +.carousel-nav:hover { + background: rgba(0, 0, 0, 0.9); +} + +/* ========================= + Responsive +========================= */ + +@media (max-width: 992px) { + .carousel-slide picture img { + height: 450px; + } + + .carousel-overlay { + width: 70%; + padding: 3rem 2rem; + } + + .carousel-overlay h1, + .carousel-overlay h2, + .carousel-overlay h3 { + font-size: 2.2rem; + } +} + +@media (max-width: 768px) { + .carousel-slide picture img { + height: 380px; + } + + .carousel-overlay { + width: 90%; + left: 50% !important; + right: auto !important; + transform: translateX(-50%) !important; + bottom: 20px; + text-align: center; + } + + .overlay-left, + .overlay-right, + .overlay-center { + left: 0; + right: 0; + transform: none; + } + + .carousel-overlay::before { + background: rgba(0, 0, 0, 0.6); + } + + .carousel-overlay h1, + .carousel-overlay h2, + .carousel-overlay h3 { + font-size: 1.8rem; + } + + .carousel-overlay p { + font-size: 1rem; + } +} \ No newline at end of file diff --git a/blocks/carousel/carousel.js b/blocks/carousel/carousel.js new file mode 100644 index 0000000..17d96d0 --- /dev/null +++ b/blocks/carousel/carousel.js @@ -0,0 +1,154 @@ +export default function decorate(block) { + const originalSlides = [...block.children]; + if (originalSlides.length <= 1) return; + + const viewport = document.createElement('div'); + viewport.className = 'carousel-viewport'; + + const track = document.createElement('div'); + track.className = 'carousel-track'; + + /* ---------------------------- + Prepare Slides (overlay logic) + ----------------------------- */ + + const getCellText = (el) => (el?.textContent?.trim() ?? ''); + const applyButtonClass = (linkCell, label) => { + const a = linkCell?.querySelector('a'); + if (a) { + a.classList.add('carousel-button'); + a.textContent = label || a.textContent || 'CTA Button'; + } + }; + + originalSlides.forEach((slide) => { + slide.classList.add('carousel-slide'); + + const cells = [...slide.children]; + const alignmentCell = cells[1]; + const titleCell = cells[2]; + const descCell = cells[3]; + const buttonLabelCell = cells[4]; + const buttonLinkCell = cells[5]; + const styleCell = cells[6]; + + const alignmentRaw = getCellText(alignmentCell).toLowerCase(); + let alignment = 'left'; + if (alignmentRaw === 'center') alignment = 'center'; + else if (alignmentRaw === 'right') alignment = 'right'; + + const title = getCellText(titleCell); + const description = getCellText(descCell); + const buttonLabel = getCellText(buttonLabelCell); + const hasLink = buttonLinkCell?.querySelector('a') || getCellText(buttonLinkCell); + const hasDetails = !!(title || description || buttonLabel || hasLink); + + if (hasDetails) { + const overlay = document.createElement('div'); + overlay.className = `carousel-overlay overlay-${alignment}`; + + [alignmentCell, titleCell, descCell, buttonLabelCell, buttonLinkCell, styleCell].forEach((c) => { + if (c?.parentNode) { + c.classList.add('carousel-overlay-cell'); + if (c === alignmentCell) c.classList.add('carousel-alignment'); + if (c === titleCell) c.classList.add('carousel-title'); + if (c === descCell) c.classList.add('carousel-desc'); + if (c === buttonLabelCell) c.classList.add('carousel-label'); + if (c === buttonLinkCell) { + c.classList.add('carousel-link'); + applyButtonClass(c, buttonLabel); + } + if (c === styleCell) c.classList.add('carousel-button-style'); + overlay.appendChild(c); + } + }); + slide.appendChild(overlay); + } else { + [alignmentCell, titleCell, descCell, buttonLabelCell, buttonLinkCell, styleCell].forEach((c) => { + if (c?.parentNode) c.remove(); + }); + } + }); + + /* ---------------------------- + Infinite Loop Setup + ----------------------------- */ + + const stripEditorAttrs = (el) => { + const nodes = [el, ...el.querySelectorAll('*')]; + nodes.forEach((node) => { + [...node.attributes].forEach((attr) => { + if (attr.name.startsWith('data-aue-') || attr.name.startsWith('data-richtext-')) { + node.removeAttribute(attr.name); + } + }); + }); + }; + + const firstClone = originalSlides[0].cloneNode(true); + const lastClone = originalSlides[originalSlides.length - 1].cloneNode(true); + + [firstClone, lastClone].forEach((clone) => { + clone.classList.add('clone'); + stripEditorAttrs(clone); + }); + + track.append(lastClone); + originalSlides.forEach((slide) => track.append(slide)); + track.append(firstClone); + + viewport.append(track); + block.textContent = ''; + block.append(viewport); + + /* ---------------------------- + Navigation + ----------------------------- */ + + const prevBtn = document.createElement('button'); + prevBtn.className = 'carousel-nav prev'; + prevBtn.innerHTML = '❮'; + + const nextBtn = document.createElement('button'); + nextBtn.className = 'carousel-nav next'; + nextBtn.innerHTML = '❯'; + + block.append(prevBtn, nextBtn); + + let currentIndex = 1; + const totalSlides = originalSlides.length; + + function setPosition(withTransition = true) { + track.style.transition = withTransition + ? 'transform 0.6s ease' + : 'none'; + + track.style.transform = `translateX(-${currentIndex * 100}%)`; + } + + setPosition(false); + + nextBtn.addEventListener('click', () => { + if (currentIndex >= totalSlides + 1) return; + currentIndex++; + setPosition(); + }); + + prevBtn.addEventListener('click', () => { + if (currentIndex <= 0) return; + currentIndex--; + setPosition(); + }); + + track.addEventListener('transitionend', () => { + if (currentIndex === totalSlides + 1) { + currentIndex = 1; + setPosition(false); + } + + if (currentIndex === 0) { + currentIndex = totalSlides; + setPosition(false); + } + }); +} \ No newline at end of file diff --git a/blocks/header/header.css b/blocks/header/header.css index 19d07bc..3383855 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -149,8 +149,8 @@ header .nav-brand { } header nav .nav-brand img { - width: 128px; - height: auto; + width: 70px; + height: 65px; } /* sections */ diff --git a/component-definition.json b/component-definition.json index 1effcf1..53912c2 100644 --- a/component-definition.json +++ b/component-definition.json @@ -301,7 +301,22 @@ "resourceType": "core/franklin/components/block/v1/block", "template": { "name": "Carousel", - "model": "carousel" + "filter": "carousel" + } + } + } + } + }, + { + "title": "Carousel Cards", + "id": "carousel-cards", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block/item", + "template": { + "name": "Carousel Cards", + "model": "carousel-cards" } } } diff --git a/component-filters.json b/component-filters.json index d125437..b475d89 100644 --- a/component-filters.json +++ b/component-filters.json @@ -38,6 +38,12 @@ "logo-card" ] }, + { + "id": "carousel", + "components": [ + "carousel-cards" + ] + }, { "id": "columns", "components": [ diff --git a/component-models.json b/component-models.json index 65222e2..3c002ee 100644 --- a/component-models.json +++ b/component-models.json @@ -401,6 +401,65 @@ "id": "carousel", "fields": [] }, + { + "id": "carousel-cards", + "fields": [ + { + "component": "reference", + "valueType": "string", + "name": "image", + "label": "Image" + }, + { + "component": "select", + "valueType": "string", + "name": "alignment", + "label": "Card Alignment", + "value": "center", + "options": [ + { + "name": "Left", + "value": "left" + }, + { + "name": "Center", + "value": "center" + }, + { + "name": "Right", + "value": "right" + } + ] + }, + { + "component": "text", + "valueType": "string", + "name": "title", + "label": "Card Title" + }, + { + "component": "richtext", + "valueType": "string", + "name": "description", + "label": "Card Description" + }, + { + "component": "text", + "valueType": "string", + "name": "buttonLabel", + "label": "Button Label" + }, + { + "component": "aem-content", + "name": "buttonLink", + "label": "Button Link" + } + ] + }, + { + "id": "logo-cards", + "fields": [] + }, { "id": "logo-card", "fields": [ From 9cf99566a892d2281af6d192c1b4c4226c9ff06c Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 16:35:31 +0530 Subject: [PATCH 04/17] fixing logo-card content tree issue --- models/_section.json | 1 + 1 file changed, 1 insertion(+) diff --git a/models/_section.json b/models/_section.json index 0cee550..c9fe067 100644 --- a/models/_section.json +++ b/models/_section.json @@ -49,6 +49,7 @@ "title", "hero", "cards", + "logo-cards", "columns", "fragment" ] From c5fe3a7df69f6b8334eee37f2d8f2d0244b72eb8 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 17:11:33 +0530 Subject: [PATCH 05/17] fixing logo-card content tree issue --- blocks/logo-cards/_logo-cards.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/blocks/logo-cards/_logo-cards.json b/blocks/logo-cards/_logo-cards.json index 588596e..7e77e0c 100644 --- a/blocks/logo-cards/_logo-cards.json +++ b/blocks/logo-cards/_logo-cards.json @@ -32,6 +32,10 @@ } ], "models": [ + { + "id": "logo-cards", + "fields": [] + }, { "id": "logo-card", "fields": [ From 7d8d71a086ee009e1e2cb590c0980dd36ba4863d Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 18:02:17 +0530 Subject: [PATCH 06/17] Update footer and header background colors to a consistent dark shade; enhance logo-cards functionality with instrumentation movement and optimized image handling. --- blocks/footer/footer.css | 2 +- blocks/header/header.css | 4 ++-- blocks/logo-cards/logo-cards.js | 13 +++++++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index d8617de..9c77da2 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,5 +1,5 @@ footer { - background-color: var(--light-color); + background-color: #131A22; font-size: var(--body-font-size-xs); } diff --git a/blocks/header/header.css b/blocks/header/header.css index 3383855..281e55a 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,6 +1,6 @@ /* header and nav layout */ header .nav-wrapper { - background-color: var(--background-color); + background-color: #131A22; width: 100%; z-index: 2; position: fixed; @@ -72,7 +72,7 @@ header nav .nav-hamburger button { border: 0; border-radius: 0; padding: 0; - background-color: var(--background-color); + background-color: #131A22; color: inherit; overflow: initial; text-overflow: initial; diff --git a/blocks/logo-cards/logo-cards.js b/blocks/logo-cards/logo-cards.js index 53325d6..50662e5 100644 --- a/blocks/logo-cards/logo-cards.js +++ b/blocks/logo-cards/logo-cards.js @@ -1,4 +1,5 @@ import { createOptimizedPicture } from '../../scripts/aem.js'; +import { moveInstrumentation } from '../../scripts/scripts.js'; export default function decorate(block) { const rows = [...block.querySelectorAll(':scope > div')]; @@ -7,6 +8,8 @@ export default function decorate(block) { rows.forEach((row) => { const li = document.createElement('li'); + moveInstrumentation(row, li); + const cells = [...row.children]; const iconCell = cells[0] || null; @@ -24,6 +27,13 @@ export default function decorate(block) { false, [{ width: '64' }], ); + const optimizedImg = optimized.querySelector('img'); + if (optimizedImg) { + moveInstrumentation(rawImg, optimizedImg); + } + const pictureOrImg = rawImg.closest('picture') || rawImg; + pictureOrImg.replaceWith(optimized); + iconWrapper = document.createElement('div'); iconWrapper.className = 'logo-card-icon'; iconWrapper.append(optimized); @@ -68,6 +78,5 @@ export default function decorate(block) { ul.appendChild(li); }); - block.innerHTML = ''; - block.appendChild(ul); + block.replaceChildren(ul); } From 13f7a87a27fdc02e3d839d13c7cca19d8710191f Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 18:15:19 +0530 Subject: [PATCH 07/17] Update header and footer background colors to a consistent light shade for improved visual coherence. --- blocks/footer/footer.css | 2 +- blocks/header/header.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 9c77da2..f6f4102 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,5 +1,5 @@ footer { - background-color: #131A22; + background-color: #d1e4f0; font-size: var(--body-font-size-xs); } diff --git a/blocks/header/header.css b/blocks/header/header.css index 281e55a..ccb92e6 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,6 +1,6 @@ /* header and nav layout */ header .nav-wrapper { - background-color: #131A22; + background-color: #d1e4f0; width: 100%; z-index: 2; position: fixed; @@ -72,7 +72,7 @@ header nav .nav-hamburger button { border: 0; border-radius: 0; padding: 0; - background-color: #131A22; + background-color: #d1e4f0; color: inherit; overflow: initial; text-overflow: initial; From a276fb4270106f70ec4c3c26e6ddf45442daa0c3 Mon Sep 17 00:00:00 2001 From: poornimapremananth <85501304+poornima-premananth@users.noreply.github.com> Date: Thu, 12 Mar 2026 18:24:14 +0530 Subject: [PATCH 08/17] Update columns.css --- blocks/columns/columns.css | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index f2b203e..e35ba72 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -5,6 +5,7 @@ .columns img { width: 100%; + border-radius: 8px; } .columns > div > div { @@ -17,6 +18,7 @@ .columns > div > .columns-img-col img { display: block; + border-radius: 8px; } @media (width >= 900px) { @@ -31,3 +33,33 @@ order: unset; } } + +.columns a:any-link { + box-sizing: border-box; + display: inline-flex; + align-items: center; + justify-content: center; + max-width: 100%; + margin: 0; + border: 2px solid var(--text-color); + border-radius: 2.4em; + padding: 0.5em 1.2em; + font: inherit; + font-weight: 500; + line-height: 1.25; + text-decoration: none; + background-color: transparent; + color: var(--text-color); + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.columns a:hover, +.columns a:focus-visible { + border-color: var(--dark-color); + background-color: var(--light-color); + color: var(--dark-color); + text-decoration: none; +} From 77fc1b6b7dd1d707adf3eb4cbc3c7ad81be0914c Mon Sep 17 00:00:00 2001 From: poornimapremananth <85501304+poornima-premananth@users.noreply.github.com> Date: Thu, 12 Mar 2026 18:31:31 +0530 Subject: [PATCH 09/17] Update columns.css --- blocks/columns/columns.css | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index e35ba72..944355b 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -63,3 +63,26 @@ color: var(--dark-color); text-decoration: none; } + +footer .columns a:any-link, +footer .footer .columns a:any-link { + border: none; + border-radius: 0; + padding: 0; + margin: 0; + background-color: transparent; + font-weight: normal; + display: inline; + text-decoration: none; + color: var(--link-color); +} + +footer .columns a:hover, +footer .columns a:focus-visible, +footer .footer .columns a:hover, +footer .footer .columns a:focus-visible { + border: none; + background-color: transparent; + color: var(--link-hover-color); + text-decoration: underline; +} From fcdddd7d5676337a9718b1fffbe8c909742e6f77 Mon Sep 17 00:00:00 2001 From: poornimapremananth <85501304+poornima-premananth@users.noreply.github.com> Date: Thu, 12 Mar 2026 18:34:14 +0530 Subject: [PATCH 10/17] Update columns.css --- blocks/columns/columns.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/columns/columns.css b/blocks/columns/columns.css index 944355b..f96f2b2 100644 --- a/blocks/columns/columns.css +++ b/blocks/columns/columns.css @@ -74,7 +74,7 @@ footer .footer .columns a:any-link { font-weight: normal; display: inline; text-decoration: none; - color: var(--link-color); + color: var(--text-color); } footer .columns a:hover, @@ -83,6 +83,6 @@ footer .footer .columns a:hover, footer .footer .columns a:focus-visible { border: none; background-color: transparent; - color: var(--link-hover-color); + color: var(--text-color); text-decoration: underline; } From 8367e4da8d89806c56a84f63ea43c0baba7f2d3b Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 18:54:21 +0530 Subject: [PATCH 11/17] Update header and footer background colors to a lighter shade for enhanced visual consistency. --- blocks/footer/footer.css | 2 +- blocks/header/header.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index f6f4102..002d880 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,5 +1,5 @@ footer { - background-color: #d1e4f0; + background-color: #b3e6ff; font-size: var(--body-font-size-xs); } diff --git a/blocks/header/header.css b/blocks/header/header.css index ccb92e6..1abbaa8 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,6 +1,6 @@ /* header and nav layout */ header .nav-wrapper { - background-color: #d1e4f0; + background-color: #b3e6ff; width: 100%; z-index: 2; position: fixed; @@ -72,7 +72,7 @@ header nav .nav-hamburger button { border: 0; border-radius: 0; padding: 0; - background-color: #d1e4f0; + background-color: #b3e6ff; color: inherit; overflow: initial; text-overflow: initial; From 8b13d05fed1dc4c8db3a3f91b1eda04001a6ac58 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 18:59:47 +0530 Subject: [PATCH 12/17] Update header and footer background colors to a consistent light shade for improved visual coherence. --- blocks/footer/footer.css | 2 +- blocks/header/header.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 002d880..db813ea 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,5 +1,5 @@ footer { - background-color: #b3e6ff; + background-color: #d6ecff; font-size: var(--body-font-size-xs); } diff --git a/blocks/header/header.css b/blocks/header/header.css index 1abbaa8..19bc63e 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,6 +1,6 @@ /* header and nav layout */ header .nav-wrapper { - background-color: #b3e6ff; + background-color: #d6ecff; width: 100%; z-index: 2; position: fixed; @@ -72,7 +72,7 @@ header nav .nav-hamburger button { border: 0; border-radius: 0; padding: 0; - background-color: #b3e6ff; + background-color: #d6ecff; color: inherit; overflow: initial; text-overflow: initial; From 63e6bc788412ffc06f608974b683af68b796e0b5 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 20:16:29 +0530 Subject: [PATCH 13/17] Add contact form component with submission endpoint and success message --- component-definition.json | 15 +++++++++++++++ component-filters.json | 1 + component-models.json | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/component-definition.json b/component-definition.json index 53912c2..343f7f4 100644 --- a/component-definition.json +++ b/component-definition.json @@ -82,6 +82,21 @@ "title": "Blocks", "id": "blocks", "components": [ + { + "title": "Contact Form", + "id": "contact-form", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Contact Form", + "model": "contact-form" + } + } + } + } + }, { "title": "Cards", "id": "cards", diff --git a/component-filters.json b/component-filters.json index b475d89..4b0ddc3 100644 --- a/component-filters.json +++ b/component-filters.json @@ -14,6 +14,7 @@ "title", "hero", "cards", + "contact-form", "logo-cards", "columns", "fragment", diff --git a/component-models.json b/component-models.json index 3c002ee..b1aead0 100644 --- a/component-models.json +++ b/component-models.json @@ -456,6 +456,25 @@ } ] }, + { + "id": "contact-form", + "fields": [ + { + "component": "text", + "valueType": "string", + "name": "endpoint", + "label": "Submission Endpoint URL", + "description": "HTTPS endpoint that receives JSON and writes to AEM or Google Sheet." + }, + { + "component": "text", + "valueType": "string", + "name": "successMessage", + "label": "Success Message", + "value": "Thank you. Your request has been submitted." + } + ] + }, { "id": "logo-cards", "fields": [] From efad92653899cb307ee355c3e1e0e345ba38e571 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 20:16:51 +0530 Subject: [PATCH 14/17] Refactor contact form component to improve submission handling and enhance user feedback with updated success message. --- blocks/contact-form/_contact-form.json | 42 +++++++ blocks/contact-form/contact-form.css | 152 +++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 blocks/contact-form/_contact-form.json create mode 100644 blocks/contact-form/contact-form.css diff --git a/blocks/contact-form/_contact-form.json b/blocks/contact-form/_contact-form.json new file mode 100644 index 0000000..2b426f0 --- /dev/null +++ b/blocks/contact-form/_contact-form.json @@ -0,0 +1,42 @@ +{ + "definitions": [ + { + "title": "Contact Form", + "id": "contact-form", + "plugins": { + "xwalk": { + "page": { + "resourceType": "core/franklin/components/block/v1/block", + "template": { + "name": "Contact Form", + "model": "contact-form" + } + } + } + } + } + ], + "models": [ + { + "id": "contact-form", + "fields": [ + { + "component": "text", + "valueType": "string", + "name": "endpoint", + "label": "Submission Endpoint URL", + "description": "HTTPS endpoint that receives JSON and writes to AEM or Google Sheet." + }, + { + "component": "text", + "valueType": "string", + "name": "successMessage", + "label": "Success Message", + "value": "Thank you. Your request has been submitted." + } + ] + } + ], + "filters": [] +} + diff --git a/blocks/contact-form/contact-form.css b/blocks/contact-form/contact-form.css new file mode 100644 index 0000000..06cddcb --- /dev/null +++ b/blocks/contact-form/contact-form.css @@ -0,0 +1,152 @@ +.contact-form-wrapper { + max-width: 640px; + margin: 0 auto; +} + +.contact-form { + padding: 24px 24px 28px; + border-radius: 16px; + background: linear-gradient(135deg, #ffffff 0%, #f5f9ff 100%); + box-shadow: + 0 10px 30px rgba(0, 0, 0, 0.06), + 0 1px 3px rgba(0, 0, 0, 0.04); +} + +.contact-form-title { + margin-top: 0; + margin-bottom: 4px; +} + +.contact-form-description { + margin-top: 0; + margin-bottom: 16px; + font-size: var(--body-font-size-s); + color: var(--dark-color); +} + +.contact-form-field { + margin-top: 12px; + display: flex; + flex-direction: column; +} + +.contact-form-field label { + margin-bottom: 4px; + font-size: var(--body-font-size-xs); + font-weight: 500; +} + +.contact-form-field input[type='text'], +.contact-form-field input[type='email'], +.contact-form-field input[type='tel'], +.contact-form-field textarea { + border-radius: 8px; + border: 1px solid #d0d7e2; + padding: 8px 10px; + font-size: var(--body-font-size-xs); + outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.contact-form-field input[type='text']:focus, +.contact-form-field input[type='email']:focus, +.contact-form-field input[type='tel']:focus, +.contact-form-field textarea:focus { + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(59, 99, 251, 0.1); +} + +.contact-form-field textarea { + resize: vertical; + min-height: 96px; +} + +.contact-form-error { + min-height: 16px; + margin-top: 4px; + font-size: 12px; + color: #c62828; +} + +.contact-form-terms { + flex-direction: row; + align-items: flex-start; + gap: 8px; +} + +.contact-form-terms input[type='checkbox'] { + margin-top: 4px; +} + +.contact-form-terms label { + margin: 0; + font-size: var(--body-font-size-xs); +} + +.contact-form-terms-link { + border: 0; + padding: 0; + margin: 0; + background: none; + color: var(--link-color); + text-decoration: underline; + cursor: pointer; + font: inherit; +} + +.contact-form-status { + margin-top: 8px; + font-size: 13px; +} + +.contact-form-status.success { + color: #2e7d32; +} + +.contact-form-status.error { + color: #c62828; +} + +.contact-form-submit { + margin-top: 16px; +} + +.contact-form-modal-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.contact-form-modal { + max-width: 480px; + width: calc(100% - 32px); + background-color: #ffffff; + border-radius: 12px; + padding: 20px 20px 16px; + box-shadow: + 0 14px 45px rgba(0, 0, 0, 0.25), + 0 10px 18px rgba(0, 0, 0, 0.22); +} + +.contact-form-modal-body { + font-size: var(--body-font-size-xs); +} + +.contact-form-modal-body ul { + padding-left: 20px; +} + +.contact-form-modal-close { + margin-top: 12px; +} + +@media (width >= 900px) { + .contact-form { + padding: 28px 32px 32px; + } +} + From a17651a2138a7184137b38dc30f48e37c7f90744 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 20:17:53 +0530 Subject: [PATCH 15/17] Enhance contact form component by adding validation checks and improving error handling for a better user experience. --- blocks/contact-form/contact-form.js | 290 ++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 blocks/contact-form/contact-form.js diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js new file mode 100644 index 0000000..7ce8c62 --- /dev/null +++ b/blocks/contact-form/contact-form.js @@ -0,0 +1,290 @@ +function buildInput(labelText, id, type = 'text', required = false) { + const field = document.createElement('div'); + field.className = 'contact-form-field'; + + const label = document.createElement('label'); + label.setAttribute('for', id); + label.textContent = labelText; + + const input = document.createElement('input'); + input.id = id; + input.name = id; + input.type = type; + if (required) input.required = true; + + const error = document.createElement('div'); + error.className = 'contact-form-error'; + error.setAttribute('aria-live', 'polite'); + + field.append(label, input, error); + return { field, input, error }; +} + +function buildTextArea(labelText, id, required = false) { + const field = document.createElement('div'); + field.className = 'contact-form-field'; + + const label = document.createElement('label'); + label.setAttribute('for', id); + label.textContent = labelText; + + const textarea = document.createElement('textarea'); + textarea.id = id; + textarea.name = id; + textarea.rows = 4; + if (required) textarea.required = true; + + const error = document.createElement('div'); + error.className = 'contact-form-error'; + error.setAttribute('aria-live', 'polite'); + + field.append(label, textarea, error); + return { field, textarea, error }; +} + +function buildTermsField() { + const wrapper = document.createElement('div'); + wrapper.className = 'contact-form-field contact-form-terms'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = 'cf-terms'; + checkbox.name = 'cf-terms'; + checkbox.required = true; + + const label = document.createElement('label'); + label.setAttribute('for', 'cf-terms'); + label.innerHTML = 'I have read and agree to the .'; + + const error = document.createElement('div'); + error.className = 'contact-form-error'; + error.setAttribute('aria-live', 'polite'); + + wrapper.append(checkbox, label, error); + return { wrapper, checkbox, error }; +} + +function buildModal() { + const overlay = document.createElement('div'); + overlay.className = 'contact-form-modal-overlay'; + overlay.setAttribute('role', 'dialog'); + overlay.setAttribute('aria-modal', 'true'); + overlay.setAttribute('aria-labelledby', 'cf-terms-title'); + + const dialog = document.createElement('div'); + dialog.className = 'contact-form-modal'; + + const title = document.createElement('h3'); + title.id = 'cf-terms-title'; + title.textContent = 'Terms and Conditions'; + + const body = document.createElement('div'); + body.className = 'contact-form-modal-body'; + body.innerHTML = ` +

Please review the trek terms and conditions before submitting the form.

+
    +
  • Ensure you are medically fit for the selected trek.
  • +
  • All bookings are subject to confirmation and availability.
  • +
  • Cancellation and refund policies apply as per company guidelines.
  • +
+ `; + + const close = document.createElement('button'); + close.type = 'button'; + close.className = 'contact-form-modal-close'; + close.textContent = 'Close'; + + dialog.append(title, body, close); + overlay.append(dialog); + + close.addEventListener('click', () => { + overlay.remove(); + }); + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + overlay.remove(); + } + }); + + return overlay; +} + +function getConfig(block) { + const rows = [...block.querySelectorAll(':scope > div')]; + const firstRowCell = rows[0]?.querySelector(':scope > div'); + const secondRowCell = rows[1]?.querySelector(':scope > div'); + + const endpoint = firstRowCell ? firstRowCell.textContent.trim() : ''; + const successMessage = secondRowCell ? secondRowCell.textContent.trim() : ''; + + return { + endpoint, + successMessage: successMessage || 'Thank you. Your request has been submitted.', + }; +} + +export default function decorate(block) { + const { endpoint, successMessage } = getConfig(block); + + block.innerHTML = ''; + + const formWrapper = document.createElement('div'); + formWrapper.className = 'contact-form-wrapper'; + + const form = document.createElement('form'); + form.className = 'contact-form'; + form.noValidate = true; + + const heading = document.createElement('h2'); + heading.className = 'contact-form-title'; + heading.textContent = 'Contact for Trek Enquiry'; + + const description = document.createElement('p'); + description.className = 'contact-form-description'; + description.textContent = 'Share your details and we will get back to you with trek information and availability.'; + + const nameField = buildInput('Name', 'cf-name', 'text', true); + const emailField = buildInput('Email', 'cf-email', 'email', true); + const phoneField = buildInput('Contact Number', 'cf-phone', 'tel', true); + const trekField = buildInput('Trek Name', 'cf-trek', 'text', true); + const commentsField = buildTextArea('Queries / Comments', 'cf-comments', true); + const termsField = buildTermsField(); + + const status = document.createElement('div'); + status.className = 'contact-form-status'; + status.setAttribute('aria-live', 'polite'); + + const submit = document.createElement('button'); + submit.type = 'submit'; + submit.className = 'contact-form-submit button'; + submit.textContent = 'Submit'; + + form.append( + heading, + description, + nameField.field, + emailField.field, + phoneField.field, + trekField.field, + commentsField.field, + termsField.wrapper, + submit, + status, + ); + + formWrapper.append(form); + block.append(formWrapper); + + // terms modal + const termsButton = termsField.wrapper.querySelector('.contact-form-terms-link'); + termsButton.addEventListener('click', () => { + const modal = buildModal(); + document.body.append(modal); + }); + + function clearErrors() { + [nameField, emailField, phoneField, trekField, commentsField, { error: termsField.error }].forEach((f) => { + if (f.error) f.error.textContent = ''; + }); + status.textContent = ''; + } + + function validate() { + clearErrors(); + let valid = true; + + if (!nameField.input.value.trim()) { + nameField.error.textContent = 'Please enter your name.'; + valid = false; + } + + const email = emailField.input.value.trim(); + if (!email) { + emailField.error.textContent = 'Please enter your email.'; + valid = false; + } else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { + emailField.error.textContent = 'Please enter a valid email address.'; + valid = false; + } + + const phone = phoneField.input.value.trim(); + if (!phone) { + phoneField.error.textContent = 'Please enter your contact number.'; + valid = false; + } else if (!/^[0-9+\-\s()]{7,20}$/.test(phone)) { + phoneField.error.textContent = 'Please enter a valid contact number.'; + valid = false; + } + + if (!trekField.input.value.trim()) { + trekField.error.textContent = 'Please enter the trek name.'; + valid = false; + } + + if (!commentsField.textarea.value.trim()) { + commentsField.error.textContent = 'Please add your queries or comments.'; + valid = false; + } + + if (!termsField.checkbox.checked) { + termsField.error.textContent = 'You must agree to the terms and conditions.'; + valid = false; + } + + return valid; + } + + async function submitForm(event) { + event.preventDefault(); + if (!validate()) return; + + if (!endpoint) { + status.textContent = 'Form endpoint is not configured.'; + status.classList.remove('success'); + status.classList.add('error'); + return; + } + + submit.disabled = true; + status.textContent = 'Submitting...'; + status.classList.remove('success', 'error'); + + const payload = { + name: nameField.input.value.trim(), + email: emailField.input.value.trim(), + phone: phoneField.input.value.trim(), + trekName: trekField.input.value.trim(), + comments: commentsField.textarea.value.trim(), + timestamp: new Date().toISOString(), + }; + + try { + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + + status.textContent = successMessage; + status.classList.remove('error'); + status.classList.add('success'); + form.reset(); + } catch (e) { + status.textContent = 'Something went wrong while submitting the form. Please try again.'; + status.classList.remove('success'); + status.classList.add('error'); + } finally { + submit.disabled = false; + } + } + + form.addEventListener('submit', submitForm); +} + From 511ae62a101d233e901d5d3dbff2af601415a353 Mon Sep 17 00:00:00 2001 From: Namita Saunshi Date: Thu, 12 Mar 2026 20:58:36 +0530 Subject: [PATCH 16/17] Refactor contact form submission to use no-cors mode, simplifying response handling and assuming success on network request completion. --- blocks/contact-form/contact-form.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/blocks/contact-form/contact-form.js b/blocks/contact-form/contact-form.js index 7ce8c62..a1a3901 100644 --- a/blocks/contact-form/contact-form.js +++ b/blocks/contact-form/contact-form.js @@ -260,18 +260,14 @@ export default function decorate(block) { }; try { - const response = await fetch(endpoint, { + await fetch(endpoint, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + mode: 'no-cors', body: JSON.stringify(payload), }); - if (!response.ok) { - throw new Error(`Request failed with status ${response.status}`); - } - + // With no-cors the response is opaque; assume success if the + // network request did not fail at the browser level. status.textContent = successMessage; status.classList.remove('error'); status.classList.add('success'); From 1e918db5f3a9b36c9c76dab38ee2a7895faca96a Mon Sep 17 00:00:00 2001 From: poornimapremananth <85501304+poornima-premananth@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:36:32 +0530 Subject: [PATCH 17/17] Update component-models.json --- component-models.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/component-models.json b/component-models.json index b1aead0..a1b05db 100644 --- a/component-models.json +++ b/component-models.json @@ -272,6 +272,10 @@ { "name": "Madurai", "value": "Madurai,IN" + }, + { + "name": "Nepal", + "value": "Nepal" } ] } @@ -516,4 +520,4 @@ } ] } -] \ No newline at end of file +]