diff --git a/blocks/header/header.css b/blocks/header/header.css
index 15b1614c..abc72404 100644
--- a/blocks/header/header.css
+++ b/blocks/header/header.css
@@ -50,7 +50,7 @@ header nav .nav-brand p {
}
header nav .nav-brand .icon, header nav .nav-brand .icon svg {
- width: 150px;
+ width: 110px;
height: 40px;
}
diff --git a/blocks/header/header.js b/blocks/header/header.js
index c564c73b..c413481b 100644
--- a/blocks/header/header.js
+++ b/blocks/header/header.js
@@ -1,5 +1,7 @@
import { decorateIcons, getMetadata, getLanguagePath } from '../../scripts/lib-franklin.js';
import { addChevronToButtons } from '../../scripts/scripts.js';
+import initLogIn from '../../scripts/login.js';
+import { getImageURL } from '../../scripts/personalisation-helpers.js';
const mobileBreakpoint = 900;
let globalWindowWidth = window.innerWidth;
@@ -106,6 +108,8 @@ function languageSwitch() {
const langSwitch = header.querySelector('.nav-tools ul');
const langLinks = langSwitch.querySelectorAll('li:not(:last-of-type) a');
+ initLogIn(langSwitch.querySelector('li:last-of-type a'));
+
const defaultLanguage = 'en';
const currentLang = document.documentElement.lang;
const isInDefaultLang = currentLang === defaultLanguage;
@@ -139,13 +143,24 @@ export default async function decorate(block) {
// fetch nav content
const resp = await fetch(`${getLanguagePath()}${getNavPath()}.plain.html`);
if (resp.ok) {
- const html = await resp.text();
+ let html = await resp.text();
const isCampaignTemplate = document.querySelector('meta[content="campaign"]');
// decorate nav DOM
const nav = document.createElement('nav');
+ if (window.personalizationData?.content?.profileImageURL) {
+ const classes = 'icon icon-netcentric-logo icon-decorated personalized';
+ const url = getImageURL(window.personalizationData.content.profileImageURL);
+ const altText = window.personalizationData.content?.firstName || 'Netcentric logo';
+ html = html.replace('', `
+
+
+
+ `);
+ }
nav.innerHTML = html;
+
const classes = isCampaignTemplate ? ['brand'] : ['brand', 'sections', 'search', 'tools'];
classes.forEach((e, j) => {
const section = nav.children[j];
diff --git a/blocks/personalization-card/personalization-card.css b/blocks/personalization-card/personalization-card.css
new file mode 100644
index 00000000..3beb5514
--- /dev/null
+++ b/blocks/personalization-card/personalization-card.css
@@ -0,0 +1,36 @@
+.personalization-card {
+ width: var(--section-width);
+ margin: 0 auto;
+ padding: 28px 0;
+ margin-block-end: 30px;
+
+ .card-offer {
+ padding-inline: 20px;
+
+ @media (min-width: 900px) {
+ display: flex;
+ column-gap: 20px;
+ }
+ }
+
+ img {
+ @media (min-width: 900px) {
+ width: 40%;
+ }
+ }
+
+ h1 {
+ color: inherit;
+ margin-block: 0;
+ }
+}
+
+.personalization-card-wrapper {
+ background-color: var(--c-dark-plum);
+ margin-inline: 0 !important;
+ width: 100% !important;
+}
+
+.personalization-card-container {
+ padding: 0 !important;
+}
\ No newline at end of file
diff --git a/blocks/personalization-card/personalization-card.js b/blocks/personalization-card/personalization-card.js
new file mode 100644
index 00000000..95aa9c9b
--- /dev/null
+++ b/blocks/personalization-card/personalization-card.js
@@ -0,0 +1,46 @@
+import { isValidJSON } from '../../scripts/personalisation-helpers.js';
+import { addChevronToButtons } from '../../scripts/scripts.js';
+
+function createCardOffer(offer) {
+ const offerElement = document.createElement('div');
+ const title = offer.title?.content ? `
${offer.title?.content}
` : '';
+ const text = offer.body?.content ? `${offer.body?.content}
` : '';
+ const button = offer.buttons[0]?.text?.content ? `${offer.buttons[0]?.text?.content}
` : '';
+ const image = offer.image?.url ? `
` : '';
+ offerElement.className = 'section dark-plum card-offer';
+ offerElement.innerHTML = `
+ ${image}
+
+ ${title}
+ ${text}
+ ${button}
+
+ `;
+ return offerElement;
+}
+
+function handleOffers(block, offer) {
+ const cardOffer = createCardOffer(offer);
+ addChevronToButtons(block);
+ block.append(cardOffer);
+}
+
+export default function decorate(block) {
+ const content = block.textContent.trim();
+ const editedContent = content.replace('Personalization Card:', '').trim();
+ if (!isValidJSON(editedContent)) {
+ block.parentElement.remove();
+ return;
+ }
+ const data = JSON.parse(editedContent);
+ block.children[0].remove();
+ data.forEach((offer) => {
+ if (Array.isArray(offer.content)) {
+ offer.content.forEach((subOffer) => {
+ handleOffers(block, subOffer);
+ });
+ } else {
+ handleOffers(block, offer.content);
+ }
+ });
+}
\ No newline at end of file
diff --git a/blocks/personalization-cbe/personalization-cbe.css b/blocks/personalization-cbe/personalization-cbe.css
new file mode 100644
index 00000000..689f8004
--- /dev/null
+++ b/blocks/personalization-cbe/personalization-cbe.css
@@ -0,0 +1,3 @@
+.personalization-cbe {
+
+}
\ No newline at end of file
diff --git a/blocks/personalization-cbe/personalization-cbe.js b/blocks/personalization-cbe/personalization-cbe.js
new file mode 100644
index 00000000..84d44d51
--- /dev/null
+++ b/blocks/personalization-cbe/personalization-cbe.js
@@ -0,0 +1,13 @@
+import { isValidJSON } from '../../scripts/personalisation-helpers.js';
+
+export default function decorate(block) {
+ const content = block.textContent.trim();
+ const editedContent = content.replace('Personalization CBE:', '').trim();
+ if (!isValidJSON(editedContent)) {
+ block.parentElement.remove();
+ return;
+ }
+ const data = JSON.parse(editedContent);
+ block.children[0].remove();
+ window.personalizationData = {...data[0]};
+}
\ No newline at end of file
diff --git a/blocks/personalization-ed/personalization-ed.css b/blocks/personalization-ed/personalization-ed.css
new file mode 100644
index 00000000..b37c879b
--- /dev/null
+++ b/blocks/personalization-ed/personalization-ed.css
@@ -0,0 +1,79 @@
+.personalization-ed {
+ display: grid;
+ grid-template-columns: 1fr;
+ column-gap: 20px;
+ row-gap: 40px;
+ margin-block-end: 80px;
+
+ > *:first-child {
+ .img-wrap {
+ position: relative;
+ }
+
+ h2 {
+ font-size: 38px;
+ }
+
+ p {
+ font-size: 30px;
+ }
+
+ @media (min-width: 900px) {
+ grid-column: span 2;
+
+ .img-mask {
+ position: absolute;
+ top: 0;
+ left: auto;
+ width: 100%;
+ height: 100%;
+ background-color: #000;
+ opacity: 0.4;
+ }
+
+ .offer-details {
+ position: absolute;
+ top: 40%;
+ z-index: 2;
+ left: 20px;
+ }
+
+ h2, p {
+ color: #fff;
+ font-weight: bold;
+ text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
+ }
+ }
+
+ @media (min-width: 1200px) {
+ .offer-details {
+ width: 65%;
+ }
+ }
+ }
+
+ > *:not(:first-child) {
+ grid-column: span 1;
+ }
+
+ > *:only-child {
+ grid-column: span 2;
+ }
+
+ @media (min-width: 900px) {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .ed-offer {
+ position: relative;
+ margin-inline: 20px;
+
+ h2 {
+ margin-block: 0;
+ }
+ }
+
+ img {
+ display: block;
+ }
+}
\ No newline at end of file
diff --git a/blocks/personalization-ed/personalization-ed.js b/blocks/personalization-ed/personalization-ed.js
new file mode 100644
index 00000000..8714d893
--- /dev/null
+++ b/blocks/personalization-ed/personalization-ed.js
@@ -0,0 +1,52 @@
+import { isValidJSON, getImageURL, getCookie } from '../../scripts/personalisation-helpers.js';
+import { createOptimizedPicture } from '../../scripts/lib-franklin.js';
+
+
+function createEdOffer(offer, index) {
+ const offerElement = document.createElement('div');
+ const imageSrc = offer.imageURL ? getImageURL(offer.imageURL) : '/insights/2023/12/media_1db1f637bcc9a28245d76086f2d141781cbcc080d.png?width=2000&format=webply&optimize=medium';
+ const subject = offer.subject ? `${offer.subject}
` : '';
+ const button = offer.buttonText ? `${offer.buttonText}
` : '';
+ const isAutenticated = getCookie('ncUser');
+ const imgMask = isAutenticated ? '' : '';
+ const imageLoading = !isAutenticated && index === 0;
+ const offerPicture = createOptimizedPicture(imageSrc, offer.offerName, imageLoading, [{ media: '(min-width: 900px)', width: '2000' }, { width: '1000' }]);
+ offerElement.classList.add('ed-offer');
+ offerElement.innerHTML = `
+
+ ${offerPicture.outerHTML}
+ ${imgMask}
+
+
+ ${subject}
+
${offer.text}
+ ${button}
+
+ `;
+ return offerElement;
+}
+
+function handleOffers(block, offer, index) {
+ const edOffer = createEdOffer(offer, index);
+ block.append(edOffer);
+}
+
+export default function decorate(block) {
+ const content = block.textContent.trim();
+ const editedContent = content.replace('Personalization ED:', '').trim();
+ if (!isValidJSON(editedContent)) {
+ block.parentElement.remove();
+ return;
+ }
+ const data = JSON.parse(editedContent);
+ block.children[0].remove();
+ data.forEach((offer, index) => {
+ if (Array.isArray(offer.content)) {
+ offer.content.forEach((subOffer, subIndex) => {
+ handleOffers(block, subOffer, subIndex);
+ });
+ } else {
+ handleOffers(block, offer.content, index);
+ }
+ });
+}
\ No newline at end of file
diff --git a/icons/netcentric-logo.svg b/icons/netcentric-logo.svg
index 124e71fc..5cc022a4 100644
--- a/icons/netcentric-logo.svg
+++ b/icons/netcentric-logo.svg
@@ -1,2 +1,249 @@
-
\ No newline at end of file
+
diff --git a/scripts/login.js b/scripts/login.js
new file mode 100644
index 00000000..5b3532f3
--- /dev/null
+++ b/scripts/login.js
@@ -0,0 +1,301 @@
+import { handleHardReload, getCookie } from './personalisation-helpers.js';
+
+let userLoggedIn = false;
+
+function resetForm(form) {
+ const inputs = form.querySelectorAll('input');
+ inputs.forEach((input) => {
+ input.value = '';
+ });
+}
+
+function resetValidationErrors(form) {
+ const errorMessages = form.querySelectorAll('.error-message');
+ const inputs = form.querySelectorAll('input');
+
+ errorMessages.forEach((errorMessage) => {
+ errorMessage.remove();
+ });
+ inputs.forEach((input) => {
+ input.classList.remove('error');
+ });
+}
+
+function validateForm(form) {
+ const inputs = form.querySelectorAll('input');
+ let isValid = true;
+ resetValidationErrors(form);
+
+ inputs.forEach((input) => {
+ const errorMessage = document.createElement('span');
+ input.before(errorMessage);
+
+ if (input.required && !input.value) {
+ errorMessage.textContent = 'This field is required';
+ errorMessage.className = 'error-message';
+ isValid = false;
+ input.classList.add('error');
+ }
+ if (input.dataset.validate === 'email') {
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailPattern.test(input.value)) {
+ errorMessage.textContent = 'Please enter a valid email address';
+ errorMessage.className = 'error-message';
+ isValid = false;
+ input.classList.add('error');
+ }
+ }
+ });
+
+ return isValid;
+}
+
+function createDialog() {
+ const dialog = document.createElement('dialog');
+ dialog.className = 'login-dialog';
+ dialog.innerHTML = `
+
+`;
+ window.document.body.appendChild(dialog);
+ return dialog;
+}
+
+function generateUUID() {
+ let d = new Date().getTime();
+
+ // Use high-precision timer if available
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
+ d += performance.now();
+ }
+
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ const r = (d + Math.random() * 16) % 16 | 0;
+ d = Math.floor(d / 16);
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
+ });
+}
+
+async function post(request, callback) {
+ try {
+ const response = await fetch(request);
+ const result = await response.json();
+ console.log("Success:", result);
+ if (callback) callback();
+ } catch (error) {
+ console.error("Error:", error);
+ }
+}
+
+function sentSignInData(form , callback) {
+ const data = {
+ "header": {
+ "schemaRef": {
+ "id": "https://ns.adobe.com/netcentricgmbh/schemas/709781f368368fceb6e2fb8c62b4def7ad2d6c2e91f98551",
+ "contentType": "application/vnd.adobe.xed-full+json;version=1"
+ },
+ "imsOrgId": "FA907D44536A3C2B0A490D4D@AdobeOrg",
+ "source": {
+ "name": "postman"
+ },
+ "datasetId": "67b1acd4627d0f2aefaca606"
+ },
+ "body": {
+ "xdmMeta": {
+ "schemaRef": {
+ "id": "https://ns.adobe.com/netcentricgmbh/schemas/709781f368368fceb6e2fb8c62b4def7ad2d6c2e91f98551",
+ "contentType": "application/vnd.adobe.xed-full+json;version=1"
+ }
+ },
+ "xdmEntity": {
+ "_id": generateUUID(),
+ "identityMap": {
+ "email": [
+ {
+ "id": form.email.value
+ }
+ ]
+ },
+ "consents": {
+ "marketing": {
+ "preferred": "email",
+ "postalMail": {
+ "val": "y"
+ },
+ "email": {
+ "val": "y"
+ },
+ "call": {
+ "val": "n"
+ },
+ "any": {
+ "val": "y"
+ }
+ }
+ },
+ "person": {
+ "name": {
+ "lastName": form.lastName.value,
+ "firstName": form.firstName.value
+ }
+ },
+ "homeAddress": {
+ "label": form.company.value
+ },
+ "personalEmail": {
+ "address": form.email.value
+ }
+ }
+ }
+ };
+
+ const request = new Request("https://dcs.adobedc.net/collection/d51a67adf7de6444b4bd4be318417667b13aea942d5d1edd548ad7174419a7be?syncValidation=true", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+
+ post(request, callback);
+}
+
+function createSignInButton(button) {
+ if (userLoggedIn) return;
+ const signInButton = document.createElement('a');
+ signInButton.href = '#';
+ signInButton.textContent = 'Sign Up';
+ button.parentElement.append(signInButton);
+
+ const dialog = document.createElement('dialog');
+ dialog.className = 'login-dialog';
+ dialog.innerHTML = `
+
+ `;
+ window.document.body.appendChild(dialog);
+ const submitButton = dialog.querySelector('button');
+
+ signInButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ dialog.showModal();
+ });
+
+ dialog.addEventListener('click', (e) => {
+ if (e.target === dialog) {
+ dialog.close();
+ resetValidationErrors(document.forms.signInForm);
+ }
+ });
+
+ submitButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ const form = document.forms.signInForm;
+ const isValid = validateForm(form);
+
+
+ if (isValid) {
+ sentSignInData(form, () => {
+ dialog.close();
+ document.cookie = `ncUser=${form.email.value}`;
+ userLoggedIn = true;
+ handleHardReload(window.location.href);
+ });
+ }
+ });
+}
+
+function setUpButtonText(button) {
+ const firstName = window.personalizationData.content?.firstName ? `${window.personalizationData.content.firstName},` : '';
+ button.textContent = userLoggedIn ? `${firstName} Log Out` : 'Log In';
+}
+
+function setUserCookie(data) {
+ document.cookie = `ncUser=${data}`;
+ userLoggedIn = true;
+}
+
+function removeUserCookie() {
+ document.cookie = 'ncUser=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
+ window.personalizationData = {};
+ userLoggedIn = false;
+}
+
+export default function initLogIn(button) {
+ const dialog = createDialog();
+ const submitButton = dialog.querySelector('button');
+ window.personalizationData = window.personalizationData || {};
+ userLoggedIn = getCookie('ncUser');
+ setUpButtonText(button);
+ button.href = '#';
+ createSignInButton(button)
+
+ button.addEventListener('click', (e) => {
+ e.preventDefault();
+
+ if (userLoggedIn) {
+ document.cookie = 'userchanged=true';
+ removeUserCookie();
+ setUpButtonText(button);
+ handleHardReload(window.location.href);
+ } else {
+ resetForm(document.forms.loginForm);
+ dialog.showModal();
+ }
+ });
+
+ dialog.addEventListener('click', (e) => {
+ if (e.target === dialog) {
+ dialog.close();
+ resetValidationErrors(document.forms.loginForm);
+ }
+ });
+
+ submitButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ const form = document.forms.loginForm;
+ const email = form.email.value;
+ const isValid = validateForm(form);
+
+ if (isValid) {
+ document.cookie = 'userchanged=; expires=Thu, 01 Jan 1970 00:00:00 UTC;';
+ setUserCookie(email);
+ setUpButtonText(button);
+ dialog.close();
+ handleHardReload(window.location.href);
+ }
+ });
+}
\ No newline at end of file
diff --git a/scripts/personalisation-helpers.js b/scripts/personalisation-helpers.js
new file mode 100644
index 00000000..254e9f48
--- /dev/null
+++ b/scripts/personalisation-helpers.js
@@ -0,0 +1,40 @@
+async function handleHardReload(url) {
+ window.location.href = url + '?nocache=' + Math.random()*1e5;
+}
+
+function isValidJSON(str) {
+ try {
+ JSON.parse(str);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function getImageURL(imageUrl) {
+ const validHosts = ['www.netcentric.biz', 'rockstar.moment-innovation.com']
+ const url = new URL(imageUrl);
+ if (validHosts.includes(url.hostname)) {
+ return url.pathname;
+ }
+ return imageUrl;
+}
+
+function getCookie(cookieStartName) {
+ const decodedCookie = decodeURIComponent(document.cookie);
+ const ca = decodedCookie.split(';');
+ for (let i = 0; i < ca.length; i++) {
+ const c = ca[i].trim();
+ if (c.startsWith(cookieStartName)) {
+ return c.substring(c.indexOf('=') + 1);
+ }
+ }
+ return '';
+}
+
+export {
+ handleHardReload,
+ isValidJSON,
+ getImageURL,
+ getCookie
+}
\ No newline at end of file
diff --git a/styles/styles.css b/styles/styles.css
index 97b0508e..d5571502 100644
--- a/styles/styles.css
+++ b/styles/styles.css
@@ -737,3 +737,93 @@ main .section.text-white {
animation: none;
}
+.login-dialog {
+ padding: 0;
+ border: none;
+ border-radius: 5px;
+
+ &::backdrop {
+ background-color: rgba(0, 0, 0, 0.5);
+ cursor: pointer;
+ }
+
+ .login-dialog-content {
+ padding: 40px;
+
+ @media (min-width: 900px) {
+ max-width: 600px;
+ }
+ }
+
+ .form-wrapper {
+ position: relative;
+
+ > div {
+ margin-bottom: 20px;
+ }
+ }
+
+ input {
+ &.error {
+ border: 1px solid var(--c-error);
+ }
+ }
+ .error-message {
+ color: var(--c-error);
+ font-size: 14px;
+ margin-top: 5px;
+ position: absolute;
+ right: 0;
+ }
+
+ input:not([type='checkbox']),
+ button {
+ box-sizing: border-box;
+ font-size: 16px;
+ line-height: 28px;
+ padding: 8px 16px;
+ margin-bottom: 20px;
+ width: 100%;
+ min-height: 44px;
+ border: unset;
+ border-radius: 4px;
+ outline-color: rgb(84 105 212 / 0.5);
+ background-color: rgb(255, 255, 255);
+ box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px;
+ }
+
+ button {
+ margin-top: 20px;
+ margin-bottom: 0;
+ background-color: var(--c-dark-plum);
+ box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
+ rgb(84, 105, 212) 0px 0px 0px 1px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(0, 0, 0, 0) 0px 0px 0px 0px,
+ rgba(60, 66, 87, 0.08) 0px 2px 5px 0px;
+ color: #fff;
+ font-weight: 600;
+ cursor: pointer;
+ }
+}
+
+.icon-netcentric-logo.personalized {
+ width: 110px !important;
+ height: 110px !important;
+ overflow: hidden;
+ border-radius: 50%;
+
+ img {
+ object-fit: cover;
+ height: 100%;
+ width: 100% ;
+ }
+}
\ No newline at end of file