From 7e0c40f5ee237f455e621512630f4784f16fa382 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Thu, 11 Jan 2024 14:47:25 -0600 Subject: [PATCH 01/12] Cherry-pick feat/login changes --- blocks/login/login-delayed.js | 96 ++++++++++ blocks/login/login.css | 327 ++++++++++++++++++++++++++++++++++ blocks/login/login.js | 125 +++++++++++++ scripts/scripts.js | 15 ++ styles/styles.css | 4 + 5 files changed, 567 insertions(+) create mode 100644 blocks/login/login-delayed.js create mode 100644 blocks/login/login.css create mode 100644 blocks/login/login.js diff --git a/blocks/login/login-delayed.js b/blocks/login/login-delayed.js new file mode 100644 index 00000000..2490bcac --- /dev/null +++ b/blocks/login/login-delayed.js @@ -0,0 +1,96 @@ +import { close, displayError, reset } from './login.js'; +import { login } from '../../scripts/apis/user.js'; + +const block = document.querySelector('.login.block'); + +function isValid(form) { + const errors = []; + const user = form.querySelector('input[name="username"]'); + if (!user.value || user.value.trim().length === 0) { + errors.push('Email address is required.'); + block.querySelector('input[name="username"]').classList.add('error'); + } + + const password = form.querySelector('input[name="password"]'); + if (!password.value || password.value.trim().length === 0) { + block.querySelector('input[name="password"]').classList.add('error'); + errors.push('Password is required.'); + } + + if (errors.length > 0) { + displayError(errors); + return false; + } + return true; +} + +/** + * Submits the form. + * + * @param {HTMLFormElement} form + */ +function submit(form) { + reset(); + + if (isValid(form)) { + const credentials = { + username: form.querySelector('input[name="username"]').value, + password: form.querySelector('input[name="password"]').value, + }; + login(credentials); + } +} + +[...block.querySelectorAll('input[name="username"], input[name="password"]')].forEach((el) => { + el.addEventListener('blur', (e) => { + const { value } = e.currentTarget; + if (!value || value.trim().length === 0) { + e.currentTarget.classList.add('error'); + } else { + e.currentTarget.classList.remove('error'); + } + }); +}); + +const rememberMe = block.querySelector('.help .remember input[type="checkbox"]'); +const pseudoRemember = block.querySelector('.remember .checkbox'); +const warning = block.querySelector('.help .warning'); + +pseudoRemember.addEventListener('click', () => { + rememberMe.click(); +}); + +rememberMe.addEventListener('change', () => { + pseudoRemember.classList.toggle('checked'); + warning.classList.toggle('visible'); +}); + +block.querySelector('a.forgot-password').addEventListener('click', (e) => { + e.currentTarget.closest('.login-form').classList.remove('open'); + const event = new Event('open-overlay'); + event.overlay = 'forgot-password'; // TODO: Change this to a constant exported from block. + document.body.dispatchEvent(event); +}); + +block.querySelector('.create-account .create-button').addEventListener('click', (e) => { + e.currentTarget.closest('.login-form').classList.remove('open'); + const event = new Event('open-overlay'); + event.overlay = 'create-account'; // TODO: Change this to a constant exported from block. + document.body.dispatchEvent(event); +}); + +block.querySelector('form').addEventListener('submit', (e) => { + e.preventDefault(); +}); + +block.querySelector('.cta a.submit').addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + submit(block.querySelector('form')); +}); + +block.querySelector('.cta a.cancel').addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + close(); +}); diff --git a/blocks/login/login.css b/blocks/login/login.css new file mode 100644 index 00000000..973557b7 --- /dev/null +++ b/blocks/login/login.css @@ -0,0 +1,327 @@ +.login.block { + display: none; + } + + .login.block.open { + display: flex; + justify-content: center; + } + + .login.block .login-overlay { + display: block; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + opacity: .5; + z-index: 1001; + background-color: var(--dark-grey); + } + + .login.block .login-form { + display: block; + position: absolute; + padding-top: 15px; + margin: 0 auto; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1001; + background-color: var(--white); + overflow-y: scroll; + } + + .login.block .login-form form { + display: flex; + padding: 5%; + width: 100%; + flex-direction: column; + } + + .login.block .login-form form h2 { + margin-top: 30px; + margin-bottom: 40px; + font-family: var(--font-family-georgia); + font-weight: var(--font-weight-bold); + font-size: var(--heading-font-size-m); + line-height: var(--line-height-m); + text-align: center; + text-transform: capitalize; + } + + .login.block .login-form form .message { + display: none; + padding: 10px 4px; + margin-bottom: 1em; + flex-direction: row; + align-items: center; + border: 1px solid; + column-gap: 4px; + } + + .login.block .login-form form .message.error { + display: flex; + color: var(--error); + border-color: var(--error); + } + + .login.block .login-form form .message.success { + display: flex; + color: var(--success); + border-color: var(--success); + } + + .login.block .login-form form .message .icon { + display: none; + align-self: flex-start; + width: 20px; + height: 20px; + } + + .login.block .login-form form .message.error .icon.error { + display: block; + } + + .login.block .login-form form .message.success .icon.success { + display: block; + } + + .login.block .login-form form .message .details { + display: flex; + flex-direction: column; + } + + .login.block .login-form form .message span { + font-family: var(--font-family-proxima); + font-size: var(--body-font-size-xs); + line-height: var(--line-height-s); + } + + .login.block .login-form form .inputs input[type="text"], + .login.block .login-form form .inputs input[type="password"] { + height: 50px; + width: 100%; + padding-left: 15px; + margin-bottom: 1em; + font-family: var(--font-family-proxima); + font-size: var(--body-font-size-s); + line-height: 50px; + color: var(--body-color); + border: 1px solid var(--dark-grey); + } + + .login.block .login-form form .inputs input[type="text"].error, + .login.block .login-form form .inputs input[type="password"].error { + color: var(--error); + background-color: var(--error-highlight); + border: 2px solid var(--error); + } + + + .login.block .login-form form .help { + display: flex; + width: 100%; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + font-size: var(--body-font-size-xs) + } + + .login.block .login-form form .help .remember { + display: flex; + align-items: center; + cursor: pointer; + flex: 0 1 auto; + } + + .login.block .login-form form .help .remember .checkbox { + cursor: pointer; + height: 12px; + width: 12px; + border: 1px solid #aaa; + margin-right: 5px; + position: relative; + border-radius: 50%; + background: white; + } + + .login.block .login-form form .help .remember .checkbox::after { + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; + background: 0; + content: ''; + border-radius: 50%; + border: 1px solid white; + } + + .login.block .login-form form .help .remember .checkbox.checked::after { + background-color: var(--black); + } + + .login.block .login-form form .help .forgot-password { + flex: 0 1 auto; + } + + .login.block .login-form form .help .warning { + display: block; + padding-top: 10px; + visibility: hidden; + margin-top: 4px; + flex: 2 2 auto; + color: var(--error); + min-height: 46px; + } + + .login.block .login-form form .help .warning.visible { + visibility: visible; + } + + .login.block .login-form form .help .remember input[type="checkbox"] { + opacity: 0; + position: absolute; + } + + .login.block .login-form form .cta { + display: flex; + padding-top: 16px; + width: 100%; + justify-content: center; + column-gap: 8px; + } + + .login.block .login-form form .cta .button-container a.button.primary { + background-color: var(--primary-color); + color: var(--white); + } + + .login.block .login-form form .cta .button-container a.button.primary:hover { + background-color: var(--white); + color: var(--body-color); + border-color: var(--grey); + } + + .login.block .login-form form .divider { + display: flex; + line-height: 23px; + align-items: center; + justify-content: center; + margin: 16px 0 8px; + } + + .login.block .login-form form .social-sign-in { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 auto; + } + + .login.block .login-form form .social-sign-in a { + display: inline-flex; + height: 50px; + margin: 5px 0; + padding: 12px 11px; + width: 83%; + align-items: center; + border-radius: 4px; + color: var(--white); + column-gap: 15px; + line-height: var(--line-height-xs); + letter-spacing: var(--letter-spacing-l); + } + + .login.block .login-form form .social-sign-in a.fb { + background-color: #1877f2; + } + + .login.block .login-form form .social-sign-in a.fb img { + height: 24px; + width: 24px; + } + + .login.block .login-form form .social-sign-in a.google { + border: 1px solid var(--dark-grey); + background-color: var(--white); + color: var(--body-color) + } + + .login.block .login-form form .social-sign-in a.google img { + height: 24px; + width: 24px; + } + + .login.block .login-form form .social-sign-in a.apple { + border: 1px solid var(--dark-grey); + background-color: var(--black); + } + + .login.block .login-form form .social-sign-in a.apple::before { + display: block; + content: '\f179'; + font-size: 24px; + width: 24px; + font-family: var(--font-family-fontawesome); + text-align: center; + } + + .login.block .login-form form .terms, + .login.block .login-form form .terms a { + padding: 28px 0 5px; + max-width: 400px; + margin: 0 auto; + font-size: var(--body-font-size-xs); + letter-spacing: var(--letter-spacing-xs); + line-height: var(--line-height-xs); + color: var(--body-color); + } + + .login.block .login-form .create-account { + padding: 16px 0; + background-color: var(--platinum); + } + + .login.block .login-form .create-account .container { + font-size: var(--body-font-size-s); + text-align: center; + color: var(--body-color); + } + + .login.block .login-form .create-account .container .create-button { + padding-top: 12px; + font-weight: var(--font-weight-semibold); + letter-spacing: var(--letter-spacing-m); + text-transform: uppercase; + text-align: center; + color: var(--body-color); + cursor: pointer; + } + + @media screen and (min-width: 600px) { + .login.block .login-form { + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1001; + width: 500px; + height: auto; + max-height: 100vh; + } + + .login.block .login-form form .message { + display: none; + padding: 10px 4px; + margin-bottom: 1em; + flex-direction: row; + align-items: center; + border: 1px solid; + column-gap: 15px; + justify-content: center; + } + } + \ No newline at end of file diff --git a/blocks/login/login.js b/blocks/login/login.js new file mode 100644 index 00000000..b21905b1 --- /dev/null +++ b/blocks/login/login.js @@ -0,0 +1,125 @@ +const LOGIN_ERROR = 'There was a problem processing your request.'; + +export function reset() { + const block = document.body.querySelector('main .login.block'); + block.querySelector('.message ').classList.remove('error'); + block.querySelector('input[name="username"]').classList.remove('error'); + block.querySelector('input[name="password"]').classList.remove('error'); +} + +/** + * Open/Show the login form. + */ +export function open() { + document.body.querySelector('main .login.block').classList.add('open'); + document.body.classList.add('no-scroll'); +} + +/** + * Close the Login form. + */ +export function close() { + document.body.querySelector('main .login.block').classList.remove('open'); + document.body.classList.remove('no-scroll'); + reset(); +} + +/** + * Displays errors for logging in. + * + * @param {string[]} errors + */ +export function displayError(errors) { + const message = document.body.querySelector('main .login.block').querySelector('.message '); + const details = message.querySelector('.details'); + const spans = []; + [LOGIN_ERROR, ...errors].forEach((m) => { + const span = document.createElement('span'); + span.textContent = m; + spans.push(span); + }); + details.replaceChildren(...spans); + message.classList.add('error'); +} + +function observeForm() { + const script = document.createElement('script'); + script.type = 'text/partytown'; + script.innerHTML = ` + const script = document.createElement('script'); + script.type = 'module'; + script.src = '${window.hlx.codeBasePath}/blocks/login/login-delayed.js'; + document.head.append(script); + `; + document.head.append(script); +} + +export default function decorate(block) { + block.innerHTML = ` +
+
+
+

Sign In

+
+ + +
+ +
+
+
+ + +
+
+
+ +
+ +
+ I forgot my password + +
+
+
+ Sign In +
+
+ Cancel +
+
+
OR
+ +
+ By clicking 'SIGN IN' or registering using any of the above third-party logins, I agree to the + Terms of Use and Privacy Policy for this website. +
+ +
+ +
+ `; + observeForm(); +} diff --git a/scripts/scripts.js b/scripts/scripts.js index 84b3b317..3f1027ca 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -6,9 +6,11 @@ import { decorateButtons, decorateIcons, decorateSections, + decorateBlock, decorateBlocks, decorateTemplateAndTheme, waitForLCP, + loadBlock, loadBlocks, loadCSS, loadScript, @@ -303,6 +305,18 @@ export function addFavIcon(href) { } } +/** + * Load the login block to the main body. + * @param main main element + * @returns {Promise} + */ +export function loadLogin(main) { + const loginBlock = buildBlock('login', ''); + main.append(loginBlock); + decorateBlock(loginBlock); + return loadBlock(loginBlock); +} + /** * Loads everything that doesn't need to be delayed. * @param {Element} doc The container element @@ -316,6 +330,7 @@ async function loadLazy(doc) { if (hash && element) element.scrollIntoView(); loadHeader(doc.querySelector('header')); loadFooter(doc.querySelector('footer')); + loadLogin(doc.querySelector('main')); loadCSS(`${window.hlx.codeBasePath}/styles/lazy-styles.css`); loadFonts(); diff --git a/styles/styles.css b/styles/styles.css index 3e3756c3..8aae75fa 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -117,6 +117,10 @@ body { display: none; } +body.no-scroll { + overflow-y: hidden; +} + body.appear { display: block; } From 716b04ca2957370a9d9afe74e0d658402f6b7bf3 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Thu, 11 Jan 2024 14:48:31 -0600 Subject: [PATCH 02/12] Fix JS errors --- blocks/property-listing/property-listing.js | 2 ++ blocks/quote-carousel/quote-carousel.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/blocks/property-listing/property-listing.js b/blocks/property-listing/property-listing.js index 240d13d5..b5587445 100644 --- a/blocks/property-listing/property-listing.js +++ b/blocks/property-listing/property-listing.js @@ -116,6 +116,8 @@ export default async function decorate(block) { const minLat = Math.min(...bbox.map((e) => e[1])); const maxLat = Math.max(...bbox.map((e) => e[1])); search = new MapSearch(minLat, minLon, maxLat, maxLon); + } else { + search = new MapSearch(0, 0, 0, 0); } search.listingTypes = buildListingTypes(entries.find(([k]) => k.match(/listing.*type/i))); diff --git a/blocks/quote-carousel/quote-carousel.js b/blocks/quote-carousel/quote-carousel.js index 0e2119ff..fc6fa63c 100644 --- a/blocks/quote-carousel/quote-carousel.js +++ b/blocks/quote-carousel/quote-carousel.js @@ -37,7 +37,7 @@ function observeCarousel() { } export default async function decorate(block) { - const blockId = crypto.randomUUID(); + const blockId = crypto && crypto.randomUUID ? crypto.randomUUID() : 'UUID-CRYPTO-NEEDS-HTTPS'; const dataUrl = block.querySelector('div > div > div:nth-child(2) > a').href; const title = getTitle(block); // generate carousel content from loaded data From e5720266b2a3cf8d3879083c7fe083022e1f41f6 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Tue, 16 Jan 2024 12:44:18 -0600 Subject: [PATCH 03/12] Missing from merge --- scripts/apis/user.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scripts/apis/user.js diff --git a/scripts/apis/user.js b/scripts/apis/user.js new file mode 100644 index 00000000..a6d8128b --- /dev/null +++ b/scripts/apis/user.js @@ -0,0 +1,34 @@ +/* + * API for interacting with the User system. + */ + +const urlParams = new URLSearchParams(window.location.search); +export const DOMAIN = urlParams.get('env') === 'stage' ? 'ignite-staging.bhhs.com' : 'www.bhhs.com'; +const API_URL = `https://${DOMAIN}/bin/bhhs`; + +/** + * Authenticates the user based on the username and password. + * + * @param {object} credentials + * @param {string} credentials.username + * @param {string} credentials.password + * @return {Promise} + */ +export async function login(credentials) { + const url = `${API_URL}/cregLoginServlet`; + const resp = await fetch(url, { + method: 'POST', + credentials: 'include', + mode: 'cors', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + }, + body: new URLSearchParams({ + Username: credentials.username, + Password: credentials.password, + }).toString(), + }); + if (resp.ok) { + console.log('Success!'); + } +} From 54f9970b225d422d9f278cda0b03214e6d0f4787 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Tue, 16 Jan 2024 12:51:04 -0600 Subject: [PATCH 04/12] Missed in the merge --- blocks/header/header.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/blocks/header/header.js b/blocks/header/header.js index d369cc32..25998d84 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -1,5 +1,6 @@ import { BREAKPOINTS } from '../../scripts/scripts.js'; import { getMetadata, decorateIcons, decorateSections } from '../../scripts/aem.js'; +import { open as openSignIn, close as closeSignIn } from '../login/login.js'; // media query match that indicates mobile/tablet width const isDesktop = BREAKPOINTS.large; @@ -63,6 +64,10 @@ function closeNavDrop(e) { */ function toggleMenu(nav, navSections, forceExpanded = null) { const expanded = forceExpanded !== null ? !forceExpanded : nav.getAttribute('aria-expanded') === 'true'; + const closing = (expanded || isDesktop.matches); + if (closing) { + closeSignIn(); + } document.body.style.overflowY = (expanded || isDesktop.matches) ? '' : 'hidden'; nav.setAttribute('aria-expanded', expanded ? 'false' : 'true'); toggleAllNavSections(navSections, expanded || isDesktop.matches ? 'false' : 'true'); @@ -137,6 +142,7 @@ function addProfileLogin(nav) { `; profileList.prepend(...profileMenu.childNodes); + profileList.querySelector('.login a').addEventListener('click', openSignIn); } /** From 3345c864bbd39fc5591e7771295f3f7ad36de253 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Tue, 16 Jan 2024 15:35:07 -0600 Subject: [PATCH 05/12] Adding delay logic to load the delayed part of the form --- blocks/login/login.js | 13 +++++++++++++ scripts/apis/user.js | 2 ++ 2 files changed, 15 insertions(+) diff --git a/blocks/login/login.js b/blocks/login/login.js index b21905b1..c3995b81 100644 --- a/blocks/login/login.js +++ b/blocks/login/login.js @@ -54,6 +54,18 @@ function observeForm() { document.head.append(script); } +let alreadyDeferred = false; +function initLogin() { + if (alreadyDeferred) { + return; + } + alreadyDeferred = true; + const script = document.createElement('script'); + script.type = 'module'; + script.src = `${window.hlx.codeBasePath}/blocks/login/login-delayed.js`; + document.head.append(script); +} + export default function decorate(block) { block.innerHTML = ` @@ -122,4 +134,5 @@ export default function decorate(block) { `; observeForm(); + initLogin(); } diff --git a/scripts/apis/user.js b/scripts/apis/user.js index a6d8128b..aee0c8b9 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -30,5 +30,7 @@ export async function login(credentials) { }); if (resp.ok) { console.log('Success!'); + } else { + console.error('Failed to login'); } } From 2ee187671810a603acd62dd0daa103bde7e59d90 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 11:32:02 -0600 Subject: [PATCH 06/12] Added more utility functions around login/logout and session state tracking for logged in user --- scripts/apis/user.js | 75 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/scripts/apis/user.js b/scripts/apis/user.js index aee0c8b9..4008f824 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -6,15 +6,67 @@ const urlParams = new URLSearchParams(window.location.search); export const DOMAIN = urlParams.get('env') === 'stage' ? 'ignite-staging.bhhs.com' : 'www.bhhs.com'; const API_URL = `https://${DOMAIN}/bin/bhhs`; +/** + * Confirms if user is logged in or not + * @return {boolean} + */ +export function isLoggedIn() { + // Check if we have userDetails in session storage + const userDetails = sessionStorage.getItem('userDetails'); + if (!userDetails) { + return false; + } + + // Check for .rwapiauth cookie + const cookies = document.cookie.split(';'); + const rwapiauthCookie = cookies.find((c) => c.trim().startsWith('.rwapiauth')); + if (rwapiauthCookie) { + return true; + } + + return false; +} + +/** + * Get user details if they are logged in, otherwise return null. + * @returns {object} user details + */ +export function getUserDetails() { + if (!isLoggedIn()) { + return null; + } + + const userDetails = sessionStorage.getItem('userDetails'); + return JSON.parse(userDetails); +} + +/** + * Logs the user out silently. + */ +export function logout() { + // Remove session storage + sessionStorage.removeItem('userDetails'); + + // Remove cookies + const cookies = document.cookie.split(';'); + cookies.forEach((c) => { + const cookie = c.trim(); + if (cookie.startsWith('.rwapiauth')) { + document.cookie = `${cookie}; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + } + }); +} + /** * Authenticates the user based on the username and password. * * @param {object} credentials * @param {string} credentials.username * @param {string} credentials.password - * @return {Promise} + * @param {function} failureCallback Callback provided reponse object. + * @return {Promise} User details if login is successful. */ -export async function login(credentials) { +export async function login(credentials, failureCallback = null) { const url = `${API_URL}/cregLoginServlet`; const resp = await fetch(url, { method: 'POST', @@ -30,7 +82,22 @@ export async function login(credentials) { }); if (resp.ok) { console.log('Success!'); - } else { - console.error('Failed to login'); + // Extract contactKey and externalID from response JSON. Store in session + const responseJson = await resp.json(); + const { contactKey, externalID } = responseJson; + const { hsfconsumerid } = JSON.parse(externalID); + const sessionData = { + contactKey, + externalID, + hsfconsumerid, + username: credentials.username, + }; + sessionStorage.setItem('userDetails', JSON.stringify(sessionData)); + return sessionData; + } + console.error('Failed to login'); + if (failureCallback) { + failureCallback(resp); } + return null; } From 81beb0d448ac1ead21e3c2cd9021e61f9cbb8ba9 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 11:43:01 -0600 Subject: [PATCH 07/12] treat invalid login as a logout as well --- scripts/apis/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/apis/user.js b/scripts/apis/user.js index 4008f824..e40c66c4 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -96,6 +96,7 @@ export async function login(credentials, failureCallback = null) { return sessionData; } console.error('Failed to login'); + logout(); if (failureCallback) { failureCallback(resp); } From 0a4160e0b47add79cc2cf28623479957fbcd4392 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 15:44:21 -0600 Subject: [PATCH 08/12] More login handling of errors, fetching profile, and showing user name in navigation --- blocks/header/header.js | 8 ++++++++ blocks/login/login-delayed.js | 15 ++++++++++++++- scripts/apis/user.js | 11 +++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/blocks/header/header.js b/blocks/header/header.js index 25998d84..19415107 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -1,6 +1,7 @@ import { BREAKPOINTS } from '../../scripts/scripts.js'; import { getMetadata, decorateIcons, decorateSections } from '../../scripts/aem.js'; import { open as openSignIn, close as closeSignIn } from '../login/login.js'; +import { logout } from '../../scripts/apis/user.js'; // media query match that indicates mobile/tablet width const isDesktop = BREAKPOINTS.large; @@ -117,6 +118,12 @@ function buildLogo() { return logo; } +function doLogout() { + const userDetailsLink = document.body.querySelector('.username a'); + userDetailsLink.textContent = 'Sign In'; + logout(); +} + /** * Adds the Profile submenu to the Nav. * @param {HTMLDivElement} nav @@ -143,6 +150,7 @@ function addProfileLogin(nav) { `; profileList.prepend(...profileMenu.childNodes); profileList.querySelector('.login a').addEventListener('click', openSignIn); + profileList.querySelector('.user-menu .logout a').addEventListener('click', doLogout); } /** diff --git a/blocks/login/login-delayed.js b/blocks/login/login-delayed.js index 2490bcac..5a32f752 100644 --- a/blocks/login/login-delayed.js +++ b/blocks/login/login-delayed.js @@ -24,6 +24,14 @@ function isValid(form) { return true; } +function loginError(response) { + if (response.status === 401) { + displayError(['Invalid username or password.']); + } else { + displayError([`There was an error logging in (${response.body})`]); + } +} + /** * Submits the form. * @@ -37,7 +45,12 @@ function submit(form) { username: form.querySelector('input[name="username"]').value, password: form.querySelector('input[name="password"]').value, }; - login(credentials); + const userDetails = login(credentials, loginError); + if (userDetails) { + close(); + const userDetailsLink = document.body.querySelector('.username a'); + userDetailsLink.textContent = userDetails?.profile?.firstName || 'Valued Customer'; + } } } diff --git a/scripts/apis/user.js b/scripts/apis/user.js index e40c66c4..6febcc65 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -40,6 +40,13 @@ export function getUserDetails() { return JSON.parse(userDetails); } +async function fetchUserProfile(username) { + const time = new Date().getTime(); + const profileResponse = await fetch(`https://www.bhhs.com/bin/bhhs/cregUserProfile?Email=${username}brendan.robert%40gmail.com&_=${time}`); + const json = profileResponse.json(); + return json; +} + /** * Logs the user out silently. */ @@ -86,10 +93,14 @@ export async function login(credentials, failureCallback = null) { const responseJson = await resp.json(); const { contactKey, externalID } = responseJson; const { hsfconsumerid } = JSON.parse(externalID); + + const profile = await fetchUserProfile(credentials.username); + const sessionData = { contactKey, externalID, hsfconsumerid, + profile, username: credentials.username, }; sessionStorage.setItem('userDetails', JSON.stringify(sessionData)); From 0a25ff46d6ff4e36f9a8acb514c96d7156403bce Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 15:53:31 -0600 Subject: [PATCH 09/12] Remove external ID from initial login --- scripts/apis/user.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/apis/user.js b/scripts/apis/user.js index 6febcc65..fed52f86 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -91,15 +91,15 @@ export async function login(credentials, failureCallback = null) { console.log('Success!'); // Extract contactKey and externalID from response JSON. Store in session const responseJson = await resp.json(); - const { contactKey, externalID } = responseJson; - const { hsfconsumerid } = JSON.parse(externalID); + const { contactKey } = responseJson; + // const { hsfconsumerid } = JSON.parse(externalID); const profile = await fetchUserProfile(credentials.username); const sessionData = { contactKey, - externalID, - hsfconsumerid, + // externalID, + // hsfconsumerid, profile, username: credentials.username, }; From 90bdc72e74a5cfb698e8764815e8a7c37535f745 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 16:11:24 -0600 Subject: [PATCH 10/12] Handle logged-in user on page load --- blocks/login/login-delayed.js | 24 +++++++++++++++++++----- scripts/apis/user.js | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/blocks/login/login-delayed.js b/blocks/login/login-delayed.js index 5a32f752..4e5b3acd 100644 --- a/blocks/login/login-delayed.js +++ b/blocks/login/login-delayed.js @@ -1,5 +1,5 @@ import { close, displayError, reset } from './login.js'; -import { login } from '../../scripts/apis/user.js'; +import { login, isLoggedIn, getUserDetails } from '../../scripts/apis/user.js'; const block = document.querySelector('.login.block'); @@ -32,12 +32,25 @@ function loginError(response) { } } +/** + * Checks if the user is logged in and updates the header accordingly. + */ +function checkForLoggedInUser() { + if (isLoggedIn()) { + const userDetailsLink = document.body.querySelector('.nav-profile .username a'); + document.body.querySelector('.nav-profile .login').style.display = 'none'; + document.body.querySelector('.nav-profile .username').style.display = 'block'; + const userDetails = getUserDetails(); + userDetailsLink.textContent = userDetails?.profile?.firstName || 'Valued Customer'; + } +} + /** * Submits the form. * * @param {HTMLFormElement} form */ -function submit(form) { +async function submit(form) { reset(); if (isValid(form)) { @@ -45,11 +58,10 @@ function submit(form) { username: form.querySelector('input[name="username"]').value, password: form.querySelector('input[name="password"]').value, }; - const userDetails = login(credentials, loginError); + const userDetails = await login(credentials, loginError); if (userDetails) { close(); - const userDetailsLink = document.body.querySelector('.username a'); - userDetailsLink.textContent = userDetails?.profile?.firstName || 'Valued Customer'; + checkForLoggedInUser(); } } } @@ -107,3 +119,5 @@ block.querySelector('.cta a.cancel').addEventListener('click', (e) => { e.stopPropagation(); close(); }); + +checkForLoggedInUser(); \ No newline at end of file diff --git a/scripts/apis/user.js b/scripts/apis/user.js index fed52f86..6d88c043 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -42,7 +42,7 @@ export function getUserDetails() { async function fetchUserProfile(username) { const time = new Date().getTime(); - const profileResponse = await fetch(`https://www.bhhs.com/bin/bhhs/cregUserProfile?Email=${username}brendan.robert%40gmail.com&_=${time}`); + const profileResponse = await fetch(`${API_URL}/cregUserProfile?Email=${encodeURIComponent(username)}&_=${time}`); const json = profileResponse.json(); return json; } From 829e4a1994ebfb12a685420e68d7b4f17e59ec62 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Wed, 17 Jan 2024 16:15:19 -0600 Subject: [PATCH 11/12] Fix lint issue --- blocks/login/login-delayed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/login/login-delayed.js b/blocks/login/login-delayed.js index 4e5b3acd..f9933533 100644 --- a/blocks/login/login-delayed.js +++ b/blocks/login/login-delayed.js @@ -120,4 +120,4 @@ block.querySelector('.cta a.cancel').addEventListener('click', (e) => { close(); }); -checkForLoggedInUser(); \ No newline at end of file +checkForLoggedInUser(); From b608f37237a4758ef428bccf1d2ebf361edd41f7 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Thu, 18 Jan 2024 09:10:35 -0600 Subject: [PATCH 12/12] Remove console logging --- scripts/apis/user.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/apis/user.js b/scripts/apis/user.js index 6d88c043..f67854ef 100644 --- a/scripts/apis/user.js +++ b/scripts/apis/user.js @@ -88,7 +88,6 @@ export async function login(credentials, failureCallback = null) { }).toString(), }); if (resp.ok) { - console.log('Success!'); // Extract contactKey and externalID from response JSON. Store in session const responseJson = await resp.json(); const { contactKey } = responseJson; @@ -106,7 +105,6 @@ export async function login(credentials, failureCallback = null) { sessionStorage.setItem('userDetails', JSON.stringify(sessionData)); return sessionData; } - console.error('Failed to login'); logout(); if (failureCallback) { failureCallback(resp);