From d37bc32eabe78a32ec34a51481c3ffc77cb3e4fa Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 23 Apr 2024 16:22:39 -0600 Subject: [PATCH 1/4] offices based on index --- blocks/cards/cards.js | 80 +++++++++++++++++++++++++++++++++++++------ helix-query.yaml | 43 ++++++++++++++++++++++- scripts/util.js | 11 ++++++ 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 06f322db..4eca1f99 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -1,15 +1,10 @@ -export default function decorate(block) { - const cards = [...block.children]; - - // Check for a title row - if (!block.classList.contains('shade-icon') && block.children[0].children.length === 1) { - const wrapper = cards.shift(); - const title = wrapper.children[0]; - title.classList.add('title'); - wrapper.remove(); - block.prepend(title); - } +import { + createOptimizedPicture, + toClassName, +} from '../../scripts/aem.js'; +import { phoneFormat } from '../../scripts/util.js'; +function buildCards(block, cards) { const list = document.createElement('div'); list.classList.add('cards-list'); block.append(list); @@ -55,3 +50,66 @@ export default function decorate(block) { }); } } + +async function fetchOffices(url) { + const response = await fetch(url); + const json = await response.json(); + return json.data; +} + +export default async function decorate(block) { + const cards = [...block.children]; + + // Check for a title row + if (!block.classList.contains('shade-icon') && block.children[0].children.length === 1) { + const wrapper = cards.shift(); + const title = wrapper.children[0]; + title.classList.add('title'); + wrapper.remove(); + block.prepend(title); + } + // Check for JSON data + if (cards.length === 1 && cards[0].children.length === 1) { + const url = cards.shift(); + const offices = await fetchOffices(url.querySelector('a[href]').href); + url.remove(); + const list = document.createElement('div'); + list.classList.add('cards-list'); + block.append(list); + offices.forEach((office) => { + const cardsItem = document.createElement('div'); + cardsItem.className = 'cards-item'; + const cardImage = document.createElement('div'); + cardImage.className = 'card-image'; + const image = createOptimizedPicture(office.image, office.location, true); + image.style = 'padding-bottom: 75%'; + const type = document.createElement('p'); + type.innerText = office.type; + cardImage.append(image, type); + cardsItem.append(cardImage); + + const cardBody = document.createElement('div'); + cardBody.className = 'card-body'; + const location = document.createElement('h3'); + location.innerText = office.location; + const address = document.createElement('p'); + address.innerHTML = office.address; + address.innerHTML += `
${office.cityStateZip}`; + address.innerHTML += `
Office: ${phoneFormat(office.phone)}`; + if (office.fax) address.innerHTML += `
Office Fax: ${phoneFormat(office.fax)}`; + const contact = document.createElement('p'); + contact.innerHTML = `${office.contactName}`; + contact.innerHTML += `
${office.contactTitle}`; + contact.innerHTML += `
${office.contactPhone}`; + contact.innerHTML += `
${office.contactEmail}`; + const contactBtn = document.createElement('p'); + contactBtn.className = 'button-container'; + contactBtn.innerHTML = `Visit Us`; + cardBody.append(location, address, contact, contactBtn); + cardsItem.append(cardBody); + list.append(cardsItem); + }); + } else { + buildCards(block, cards); + } +} diff --git a/helix-query.yaml b/helix-query.yaml index f19c7c69..3f3c9453 100644 --- a/helix-query.yaml +++ b/helix-query.yaml @@ -41,4 +41,45 @@ indices: value: attribute(el, "content") liveby-community: select: head > meta[name="liveby-community"] - value: attribute(el, "content") \ No newline at end of file + value: attribute(el, "content") + offices: + include: + - /offices/* + target: /offices.json + properties: + lastModified: + select: none + value: parseTimestamp(headers["last-modified"], "ddd, DD MMM YYYY hh:mm:ss GMT") + location: + select: head > meta[name="location"] + value: attribute(el, "content") + type: + select: head > meta[name="type"] + value: attribute(el, "content") + address: + select: head > meta[name="address"] + value: attribute(el, "content") + cityStateZip: + select: head > meta[name="city-state-zip"] + value: attribute(el, "content") + phone: + select: head > meta[name="phone"] + value: attribute(el, "content") + fax: + select: head > meta[name="fax"] + value: attribute(el, "content") + contactName: + select: head > meta[name="contact-name"] + value: attribute(el, "content") + contactTitle: + select: head > meta[name="contact-title"] + value: attribute(el, "content") + contactPhone: + select: head > meta[name="contact-phone"] + value: attribute(el, "content") + contactEmail: + select: head > meta[name="contact-email"] + value: attribute(el, "content") + image: + select: head > meta[property="og:image"] + value: match(attribute(el, "content"), "https:\/\/[^/]+(/.*)") diff --git a/scripts/util.js b/scripts/util.js index a2783b48..150548dc 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -95,6 +95,17 @@ export function getEnvType(hostname = window.location.hostname) { return fqdnToEnvType[hostname] || 'dev'; } +export function phoneFormat(num) { + // Remove any non-digit characters from the string + let phoneNum = num.replace(/\D/g, ''); + if (!phoneNum) { + return ''; + } + // Format the phoneNumber according to (XXX) XXX-XXXX + phoneNum = phoneNum.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); + return phoneNum; +} + const Util = { getSpinner, showModal, From f0629a381d94a58fe0e64050c44b4626a2720735 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 30 Apr 2024 19:05:16 -0600 Subject: [PATCH 2/4] moved offices to block --- blocks/cards/cards.css | 52 ++++++++++++++++- blocks/cards/cards.js | 125 +++++++++++++++++++++++++++++------------ 2 files changed, 139 insertions(+), 38 deletions(-) diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 07888440..6ab0bcba 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -98,14 +98,54 @@ margin: 8px 0 24px; } +.cards.block .cards-list .cards-item .card-body a.name { + text-decoration: none; + font-family: var(--font-family-secondary); + font-size: var(--body-font-size-l); + line-height: var(--line-height-s); + color: var(--body-color); +} + +.cards.block .cards-list .cards-item .card-body div.title, +.cards.block .cards-list .cards-item .card-body div.teamname, +.cards.block .cards-list .cards-item .card-body div.phone, +.cards.block .cards-list .cards-item .card-body div.license { + font-size: var(--body-font-size-xxs); + font-weight: 600; + line-height: var(--line-height-m); + letter-spacing: var(--letter-spacing-m); + text-transform: uppercase; + color: var(--body-color); + padding: 1em 0; +} + +.cards.block .cards-list .cards-item .card-body div.teamname { + font-weight: 700; + letter-spacing: normal; + text-transform: none; +} + +.cards.block .cards-list .cards-item .card-body div.phone, +.cards.block .cards-list .cards-item .card-body div.license { + font-weight: var(--font-weight-normal); + text-transform: none; + letter-spacing: normal; + padding: 0; +} + .tertiary-background .cards.block .cards-list .cards-item .card-image p { background-color: var(--tertiary-color); } +.cards.block .cards-list .cards-item .card-body { + flex-grow: 1; +} + .cards.block .cards-list .cards-item .card-body .button-container { - margin: 16px 0 0 ; + margin-top: auto; height: 40px; display: flex; + align-self: flex-end; } /* Icons variation */ @@ -252,6 +292,16 @@ margin-bottom: 20px; } + .cards.block.agents .cards-list { + flex-flow: row wrap; + } + + .cards.block.agents .cards-list .cards-item { + flex: 0 0 20%; + margin-bottom: 20px; + min-width: 220px; + } + .cards.block.cards-1-cols .cards-list .cards-item .card-image { max-height: 500px; } diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 4eca1f99..db840d3c 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -51,7 +51,85 @@ function buildCards(block, cards) { } } -async function fetchOffices(url) { +function buildOfficeCards(list, data) { + data.forEach((item) => { + const cardsItem = document.createElement('div'); + cardsItem.className = 'cards-item'; + const cardImage = document.createElement('div'); + cardImage.className = 'card-image'; + const image = createOptimizedPicture(item.image, item.location, true); + image.style = 'padding-bottom: 75%'; + const type = document.createElement('p'); + type.innerText = item.type; + cardImage.append(image, type); + cardsItem.append(cardImage); + const cardBody = document.createElement('div'); + cardBody.className = 'card-body'; + const location = document.createElement('h3'); + location.innerText = item.location; + const address = document.createElement('p'); + address.innerHTML = item.address; + address.innerHTML += `
${item.cityStateZip}`; + address.innerHTML += `
Office: ${phoneFormat(item.phone)}`; + if (item.fax) address.innerHTML += `
Office Fax: ${phoneFormat(item.fax)}`; + const contact = document.createElement('p'); + contact.innerHTML = `${item.contactName}`; + contact.innerHTML += `
${item.contactTitle}`; + contact.innerHTML += `
${item.contactPhone}`; + contact.innerHTML += `
${item.contactEmail}`; + const contactBtn = document.createElement('p'); + contactBtn.className = 'button-container'; + contactBtn.innerHTML = `Visit Us`; + cardBody.append(location, address, contact, contactBtn); + cardsItem.append(cardBody); + list.append(cardsItem); + }); +} + +function buildAgentCards(list, data) { + const { pathname } = window.location; + const parts = pathname.split('/'); + const pageName = parts[parts.length - 1]; + const filteredData = data.filter((item) => item.office.toLowerCase() === pageName); + filteredData.forEach((item) => { + const cardsItem = document.createElement('div'); + cardsItem.className = 'cards-item'; + const cardImage = document.createElement('div'); + cardImage.className = 'card-image'; + const tmpImage = 'https://main--hsf-commonmoves--hlxsites.hlx.page/media/images/no-profile-image.png'; + const image = createOptimizedPicture(tmpImage, item.name, true); + image.style = 'padding-bottom: 75%'; + + cardImage.append(image); + cardsItem.append(cardImage); + const cardBody = document.createElement('div'); + cardBody.className = 'card-body'; + const nameLink = document.createElement('a'); + nameLink.className = 'name'; + nameLink.href = item.profile; + nameLink.innerText = item.name; + const title = document.createElement('div'); + title.className = 'title'; + title.innerText = item.title; + const team = document.createElement('div'); + team.className = 'teamname'; + team.innerText = item.team; + const phone = document.createElement('div'); + phone.className = 'phone'; + phone.innerText = phoneFormat(item.phone); + const license = document.createElement('div'); + license.className = 'license'; + license.innerText = item.license; + const contactBtn = document.createElement('p'); + contactBtn.className = 'button-container'; + contactBtn.innerHTML = `Agent Detail`; + cardBody.append(nameLink, title, team, phone, license, contactBtn); + cardsItem.append(cardBody); + list.append(cardsItem); + }); +} + +async function fetchIndex(url) { const response = await fetch(url); const json = await response.json(); return json.data; @@ -70,45 +148,18 @@ export default async function decorate(block) { } // Check for JSON data if (cards.length === 1 && cards[0].children.length === 1) { - const url = cards.shift(); - const offices = await fetchOffices(url.querySelector('a[href]').href); - url.remove(); + const link = cards.shift(); + const url = new URL(link.querySelector('a[href]').href); + const jsonData = await fetchIndex(url.href); + link.remove(); const list = document.createElement('div'); list.classList.add('cards-list'); block.append(list); - offices.forEach((office) => { - const cardsItem = document.createElement('div'); - cardsItem.className = 'cards-item'; - const cardImage = document.createElement('div'); - cardImage.className = 'card-image'; - const image = createOptimizedPicture(office.image, office.location, true); - image.style = 'padding-bottom: 75%'; - const type = document.createElement('p'); - type.innerText = office.type; - cardImage.append(image, type); - cardsItem.append(cardImage); - - const cardBody = document.createElement('div'); - cardBody.className = 'card-body'; - const location = document.createElement('h3'); - location.innerText = office.location; - const address = document.createElement('p'); - address.innerHTML = office.address; - address.innerHTML += `
${office.cityStateZip}`; - address.innerHTML += `
Office: ${phoneFormat(office.phone)}`; - if (office.fax) address.innerHTML += `
Office Fax: ${phoneFormat(office.fax)}`; - const contact = document.createElement('p'); - contact.innerHTML = `${office.contactName}`; - contact.innerHTML += `
${office.contactTitle}`; - contact.innerHTML += `
${office.contactPhone}`; - contact.innerHTML += `
${office.contactEmail}`; - const contactBtn = document.createElement('p'); - contactBtn.className = 'button-container'; - contactBtn.innerHTML = `Visit Us`; - cardBody.append(location, address, contact, contactBtn); - cardsItem.append(cardBody); - list.append(cardsItem); - }); + if (url.searchParams.has('sheet')) { + buildAgentCards(list, jsonData); + } else { + buildOfficeCards(list, jsonData); + } } else { buildCards(block, cards); } From 4758b44aa631c6ccc7b8c3fcd00077b956566507 Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 30 Apr 2024 19:09:10 -0600 Subject: [PATCH 3/4] add missing block --- blocks/offices/offices.css | 127 +++++++++++++++++++++++++++++++++++++ blocks/offices/offices.js | 113 +++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 blocks/offices/offices.css create mode 100644 blocks/offices/offices.js diff --git a/blocks/offices/offices.css b/blocks/offices/offices.css new file mode 100644 index 00000000..fd26f5c2 --- /dev/null +++ b/blocks/offices/offices.css @@ -0,0 +1,127 @@ +.offices.block { + padding-bottom: 32px; +} + +.offices.block .title { + padding: 2em 0; +} + +.offices.block .cards-list .cards-item { + display: flex; + flex-direction: column; + position: relative; + color: #2a2223; +} + +.offices.block.agents .cards-list .cards-item { + display: inline-block; + width: 100%; + border-bottom: 1px solid var(--grey); + padding: 16px 0; +} + +.offices.block.agents .cards-list .cards-item:first-of-type { + border-top: 1px solid var(--grey); +} + +.offices.block .cards-list .cards-item .card-image { + position: relative; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.offices.block.agents .cards-list .cards-item .card-image { + display: inline-block; + width: 33%; +} + +.offices.block .cards-list .cards-item .card-image picture { + display: block; + position: relative; + height: 100%; + width: 100%; +} + +.offices.block .cards-list .cards-item .card-image img { + position: absolute; + bottom: 0; + left: 0; + object-fit: cover; + object-position: center; + width: 100%; + height: 100%; +} + +.offices.block.agents .cards-list .cards-item .card-body { + display: inline-block; + width: 66%; + padding-left: 15px; +} + +.offices.block.agents .cards-list .cards-item .card-body .name { + font-family: var(--font-family-secondary); + text-decoration: none; + color: #2a2223; +} + +.offices.block.agents .cards-list .cards-item .card-body .title { + padding: 1em 0; + font-size: var(--body-font-size-xxs); + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-m); + letter-spacing: var(--letter-spacing-reg); + text-transform: uppercase; +} +.offices.block.agents .cards-list .cards-item .card-body .teamname { + font-family: var(--font-family-primary); + font-size: var(--body-font-size-xxs); + font-weight: var(--font-weight-semibold); + line-height: 130%; +} + +.offices.block.agents .cards-list .cards-item .card-body div { + font-family: var(--font-family-primary); + font-size: var(--body-font-size-xxs); + line-height: var(--line-height-s); + margin: 8px 0; +} + +.offices.block .cards-list .cards-item .card-body p { + font-size: var(--body-font-size-xs); + letter-spacing: var(--letter-spacing-xs); + margin: 8px 0 24px; +} + +.offices.block .cards-list .cards-item .card-body .button-container { + margin-top: auto; + height: 40px; +} + +@media (min-width: 900px) { + .offices.block.agents .cards-list .cards-item, + .offices.block.agents .cards-list .cards-item:first-of-type { + border: none; + } +} + +@media (min-width: 900px) { + .offices.block .cards-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + } + + .offices.block .cards-list .cards-item { + margin-bottom: 0; + } + + .offices.block .cards-list .cards-item .card-body { + flex-grow: 1; + } + + .offices.block .cards-list .cards-item .card-body p { + font-size: var(--body-font-size-s); + } +} diff --git a/blocks/offices/offices.js b/blocks/offices/offices.js new file mode 100644 index 00000000..a4c5b764 --- /dev/null +++ b/blocks/offices/offices.js @@ -0,0 +1,113 @@ +import { + createOptimizedPicture, + toClassName, +} from '../../scripts/aem.js'; +import { phoneFormat } from '../../scripts/util.js'; + +function buildOfficeCards(list, data) { + data.forEach((item) => { + const cardsItem = document.createElement('div'); + cardsItem.className = 'cards-item'; + const cardImage = document.createElement('div'); + cardImage.className = 'card-image'; + const image = createOptimizedPicture(item.image, item.location, true); + image.style = 'padding-bottom: 75%'; + const type = document.createElement('p'); + type.innerText = item.type; + cardImage.append(image, type); + cardsItem.append(cardImage); + const cardBody = document.createElement('div'); + cardBody.className = 'card-body'; + const location = document.createElement('h3'); + location.innerText = item.location; + const address = document.createElement('p'); + address.innerHTML = item.address; + address.innerHTML += `
${item.cityStateZip}`; + address.innerHTML += `
Office: ${phoneFormat(item.phone)}`; + if (item.fax) address.innerHTML += `
Office Fax: ${phoneFormat(item.fax)}`; + const contact = document.createElement('p'); + contact.innerHTML = `${item.contactName}`; + contact.innerHTML += `
${item.contactTitle}`; + contact.innerHTML += `
${item.contactPhone}`; + contact.innerHTML += `
${item.contactEmail}`; + const contactBtn = document.createElement('p'); + contactBtn.className = 'button-container'; + contactBtn.innerHTML = `Visit Us`; + cardBody.append(location, address, contact); + cardsItem.append(cardBody, contactBtn); + list.append(cardsItem); + }); +} + +function buildAgentCards(list, data) { + const { pathname } = window.location; + const parts = pathname.split('/'); + const pageName = parts[parts.length - 1]; + const filteredData = data.filter((item) => item.office.toLowerCase() === pageName); + filteredData.forEach((item) => { + const cardsItem = document.createElement('div'); + cardsItem.className = 'cards-item'; + const cardImage = document.createElement('div'); + cardImage.className = 'card-image'; + const tmpImage = 'https://main--hsf-commonmoves--hlxsites.hlx.page/media/images/no-profile-image.png'; + const image = createOptimizedPicture(tmpImage, item.name, true); + image.style = 'padding-bottom: 75%'; + + cardImage.append(image); + cardsItem.append(cardImage); + const cardBody = document.createElement('div'); + cardBody.className = 'card-body'; + const nameLink = document.createElement('a'); + nameLink.className = 'name'; + nameLink.href = item.profile; + nameLink.innerText = item.name; + const title = document.createElement('div'); + title.className = 'title'; + title.innerText = item.title; + const team = document.createElement('div'); + team.className = 'teamname'; + team.innerText = item.team; + const phone = document.createElement('div'); + phone.className = 'phone'; + phone.innerText = phoneFormat(item.phone); + const license = document.createElement('div'); + license.className = 'license'; + license.innerText = item.license; + const contactBtn = document.createElement('p'); + contactBtn.className = 'button-container'; + contactBtn.innerHTML = `Agent Detail`; + cardBody.append(nameLink, title, team, phone, license, contactBtn); + cardsItem.append(cardBody); + list.append(cardsItem); + }); +} + +async function fetchIndex(url) { + const response = await fetch(url); + const json = await response.json(); + return json.data; +} + +export default async function decorate(block) { + const cards = [...block.children]; + // Check for a title row + if (block.children[0].children.length === 2) { + const wrapper = cards.shift(); + const title = wrapper.children[1]; + title.classList.add('title'); + wrapper.remove(); + block.prepend(title); + } + const link = cards.shift(); + const url = new URL(link.querySelector('a[href]').href); + const jsonData = await fetchIndex(url.href); + link.remove(); + const list = document.createElement('div'); + list.classList.add('cards-list'); + block.append(list); + if (url.searchParams.has('sheet')) { + buildAgentCards(list, jsonData); + } else { + buildOfficeCards(list, jsonData); + } +} From 509da012709b1b0bb3f3fb3fc820fa5d3d6bdeec Mon Sep 17 00:00:00 2001 From: Rob Rusher Date: Tue, 30 Apr 2024 19:12:12 -0600 Subject: [PATCH 4/4] css linting --- blocks/offices/offices.css | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/offices/offices.css b/blocks/offices/offices.css index fd26f5c2..c19cacff 100644 --- a/blocks/offices/offices.css +++ b/blocks/offices/offices.css @@ -74,6 +74,7 @@ letter-spacing: var(--letter-spacing-reg); text-transform: uppercase; } + .offices.block.agents .cards-list .cards-item .card-body .teamname { font-family: var(--font-family-primary); font-size: var(--body-font-size-xxs);