Skip to content

Commit

Permalink
Gift cards/add recipient (#2412)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stu Freen committed Mar 21, 2023
1 parent 8a81d73 commit e808393
Show file tree
Hide file tree
Showing 63 changed files with 1,334 additions and 346 deletions.
2 changes: 1 addition & 1 deletion assets/component-cart-items.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@

.product-option {
font-size: 1.4rem;
word-break: break-all;
word-break: break-word;
line-height: calc(1 + 0.5 / var(--font-body-scale));
}

Expand Down
3 changes: 2 additions & 1 deletion assets/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ const ON_CHANGE_DEBOUNCE_TIMER = 300;
const PUB_SUB_EVENTS = {
cartUpdate: 'cart-update',
quantityUpdate: 'quantity-update',
variantChange: 'variant-change'
variantChange: 'variant-change',
cartError: 'cart-error'
};
7 changes: 6 additions & 1 deletion assets/product-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ if (!customElements.get('product-form')) {
this.cart = document.querySelector('cart-notification') || document.querySelector('cart-drawer');
this.submitButton = this.querySelector('[type="submit"]');
if (document.querySelector('cart-drawer')) this.submitButton.setAttribute('aria-haspopup', 'dialog');

this.hideErrors = this.dataset.hideErrors === 'true';
}

onSubmitHandler(evt) {
Expand Down Expand Up @@ -37,6 +39,7 @@ if (!customElements.get('product-form')) {
.then((response) => response.json())
.then((response) => {
if (response.status) {
publish(PUB_SUB_EVENTS.cartError, {source: 'product-form', productVariantId: formData.get('id'), errors: response.description, message: response.message});
this.handleErrorMessage(response.description);

const soldOutMessage = this.submitButton.querySelector('.sold-out-message');
Expand All @@ -51,7 +54,7 @@ if (!customElements.get('product-form')) {
return;
}

if (!this.error) publish(PUB_SUB_EVENTS.cartUpdate, {source: 'product-form'});
if (!this.error) publish(PUB_SUB_EVENTS.cartUpdate, {source: 'product-form', productVariantId: formData.get('id')});
this.error = false;
const quickAddModal = this.closest('quick-add-modal');
if (quickAddModal) {
Expand All @@ -75,6 +78,8 @@ if (!customElements.get('product-form')) {
}

handleErrorMessage(errorMessage = false) {
if (this.hideErrors) return;

this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector('.product-form__error-message-wrapper');
if (!this.errorMessageWrapper) return;
this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector('.product-form__error-message');
Expand Down
139 changes: 139 additions & 0 deletions assets/recipient-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
if (!customElements.get('recipient-form')) {
customElements.define('recipient-form', class RecipientForm extends HTMLElement {
constructor() {
super();
this.checkboxInput = this.querySelector(`#Recipient-Checkbox-${ this.dataset.sectionId }`);
this.checkboxInput.disabled = false;
this.hiddenControlField = this.querySelector(`#Recipient-Control-${ this.dataset.sectionId }`);
this.hiddenControlField.disabled = true;
this.emailInput = this.querySelector(`#Recipient-email-${ this.dataset.sectionId }`);
this.nameInput = this.querySelector(`#Recipient-name-${ this.dataset.sectionId }`);
this.messageInput = this.querySelector(`#Recipient-message-${ this.dataset.sectionId }`);
this.errorMessageWrapper = this.querySelector('.product-form__recipient-error-message-wrapper');
this.errorMessageList = this.errorMessageWrapper?.querySelector('ul');
this.errorMessage = this.errorMessageWrapper?.querySelector('.error-message');
this.defaultErrorHeader = this.errorMessage?.innerText;
this.currentProductVariantId = this.dataset.productVariantId;
this.addEventListener('change', this.onChange.bind(this));
}

cartUpdateUnsubscriber = undefined;
variantChangeUnsubscriber = undefined;
cartErrorUnsubscriber = undefined;

connectedCallback() {
this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, (event) => {
if (event.source === 'product-form' && event.productVariantId.toString() === this.currentProductVariantId) {
this.resetRecipientForm();
}
});

this.variantChangeUnsubscriber = subscribe(PUB_SUB_EVENTS.variantChange, (event) => {
if (event.data.sectionId === this.dataset.sectionId) {
this.currentProductVariantId = event.data.variant.id.toString();
}
});

this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartError, (event) => {
if (event.source === 'product-form' && event.productVariantId.toString() === this.currentProductVariantId) {
this.displayErrorMessage(event.message, event.errors);
}
});
}

disconnectedCallback() {
if (this.cartUpdateUnsubscriber) {
this.cartUpdateUnsubscriber();
}

if (this.variantChangeUnsubscriber) {
this.variantChangeUnsubscriber();
}

if (this.cartErrorUnsubscriber) {
this.cartErrorUnsubscriber();
}
}

onChange() {
if (!this.checkboxInput.checked) {
this.clearInputFields();
this.clearErrorMessage();
}
}

clearInputFields() {
this.emailInput.value = '';
this.nameInput.value = '';
this.messageInput.value = '';
}

displayErrorMessage(title, body) {
this.clearErrorMessage();
this.errorMessageWrapper.hidden = false;
if (typeof body === 'object') {
this.errorMessage.innerText = this.defaultErrorHeader;
return Object.entries(body).forEach(([key, value]) => {
const errorMessageId = `RecipientForm-${ key }-error-${ this.dataset.sectionId }`
const fieldSelector = `#Recipient-${ key }-${ this.dataset.sectionId }`;
const placeholderElement = this.querySelector(`${fieldSelector}`);
const label = placeholderElement?.getAttribute('placeholder') || key;
const message = `${label} ${value}`;
const errorMessageElement = this.querySelector(`#${errorMessageId}`);
const errorTextElement = errorMessageElement?.querySelector('.error-message')
if (!errorTextElement) return;

if (this.errorMessageList) {
this.errorMessageList.appendChild(this.createErrorListItem(fieldSelector, message));
}

errorTextElement.innerText = `${message}.`;
errorMessageElement.classList.remove('hidden');

const inputElement = this[`${key}Input`];
if (!inputElement) return;

inputElement.setAttribute('aria-invalid', true);
inputElement.setAttribute('aria-describedby', errorMessageId);
});
}

this.errorMessage.innerText = body;
}

createErrorListItem(target, message) {
const li = document.createElement('li');
const a = document.createElement('a');
a.setAttribute('href', target);
a.innerText = message;
li.appendChild(a);
li.className = "error-message";
return li;
}

clearErrorMessage() {
this.errorMessageWrapper.hidden = true;

if (this.errorMessageList) this.errorMessageList.innerHTML = '';

this.querySelectorAll('.recipient-fields .form__message').forEach(field => {
field.classList.add('hidden');
const textField = field.querySelector('.error-message');
if (textField) textField.innerText = '';
});

[this.emailInput, this.messageInput, this.nameInput].forEach(inputElement => {
inputElement.setAttribute('aria-invalid', false);
inputElement.removeAttribute('aria-describedby');
});
}

resetRecipientForm() {
if (this.checkboxInput.checked) {
this.checkboxInput.checked = false;
this.clearInputFields();
this.clearErrorMessage();
}
}
});
}
128 changes: 127 additions & 1 deletion assets/section-main-product.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
flex: 0 0 100%;
padding: 0;
margin: 0 0 1.2rem 0;
max-width: 37rem;
max-width: 44rem;
min-width: fit-content;
border: none;
}
Expand Down Expand Up @@ -1473,3 +1473,129 @@ a.product__text {
display: none;
}
}

/* Recipient form */
.recipient-form {
/* (2.88[line-height] - 1.6rem) / 2 */
--recipient-checkbox-margin-top: 0.64rem;

display: block;
position: relative;
max-width: 44rem;
margin-bottom: 2.5rem;
}

.recipient-form-field-label {
margin: 0.6rem 0;
}

.recipient-form-field-label--space-between {
display: flex;
justify-content: space-between;
}

.recipient-checkbox {
flex-grow: 1;
font-size: 1.6rem;
display: flex;
word-break: break-word;
align-items: flex-start;
max-width: inherit;
position: relative;
}

.no-js .recipient-checkbox {
display: none;
}

.recipient-form > input[type='checkbox'] {
position: absolute;
width: 1.6rem;
height: 1.6rem;
margin: var(--recipient-checkbox-margin-top) 0;
top: 0;
left: 0;
z-index: -1;
appearance: none;
-webkit-appearance: none;
}

.recipient-fields__field {
margin: 0 0 2rem 0;
}

.recipient-fields .field__label {
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - 3.5rem);
overflow: hidden;
}

.recipient-checkbox > svg {
margin-top: var(--recipient-checkbox-margin-top);
margin-right: 1.2rem;
flex-shrink: 0;
}

.recipient-form .icon-checkmark {
visibility: hidden;
position: absolute;
left: 0.28rem;
z-index: 5;
top: 0.4rem;
}

.recipient-form > input[type='checkbox']:checked + label .icon-checkmark {
visibility: visible;
}

.js .recipient-fields {
display: none;
}

.recipient-fields hr {
margin: 1.6rem auto;
}

.recipient-form > input[type='checkbox']:checked ~ .recipient-fields {
display: block;
animation: animateMenuOpen var(--duration-default) ease;
}
.recipient-form > input[type='checkbox']:not(:checked, :disabled) ~ .recipient-fields ,
.recipient-email-label {
display: none;
}

.js .recipient-email-label.required,
.no-js .recipient-email-label.optional {
display: inline;
}

.recipient-form ul {
line-height: calc(1 + 0.6 / var(--font-body-scale));
padding-left: 4.4rem;
text-align: left;
}

.recipient-form ul a {
display: inline;
}

.recipient-form .error-message::first-letter {
text-transform: capitalize;
}

@media screen and (forced-colors: active) {
.recipient-fields > hr {
border-top: 0.1rem solid rgb(var(--color-background));
}

.recipient-checkbox > svg {
background-color: inherit;
border: 0.1rem solid rgb(var(--color-background));
}

.recipient-form > input[type='checkbox']:checked + label .icon-checkmark {
border: none;
}
}

1 comment on commit e808393

@Walla-dev
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the backend logic handled for sending gift cards. I've made these updates in my vintage theme accordingly, but I can't get the actual send to occur.

Please sign in to comment.