192 lines
6.5 KiB
TypeScript
192 lines
6.5 KiB
TypeScript
import * as React from 'react';
|
|
import { DataContext } from '../context';
|
|
import * as ReactDOM from 'react-dom';
|
|
import { ADD_CART_COUPON } from '@assets/js-shop/query';
|
|
import { IData } from '@assets/js-shop/GraphQLInterfaces';
|
|
import { ShopUtils, translate as _t } from '@assets/js-shop/ShopUtils';
|
|
import { ToastStatus, ToastType } from '@assets/js-shop/Interfaces';
|
|
import { Icon } from '@assets/shared/icon/Icon';
|
|
import { IconType } from '@assets/shared/icon/types';
|
|
import { MouseEvent } from 'react';
|
|
|
|
export enum ICouponState{
|
|
None,
|
|
Loading,
|
|
Failed,
|
|
}
|
|
export interface ICouponItem {
|
|
buttonElem: HTMLElement;
|
|
code: string;
|
|
state: ICouponState;
|
|
discountTemplateVersion: number;
|
|
message?: string;
|
|
}
|
|
interface ICouponsState {
|
|
coupons: ICouponItem[];
|
|
}
|
|
|
|
const couponsWrapperSelector = '[data-product-add-coupon-to-cart]';
|
|
|
|
export class Coupons extends React.PureComponent<{}, ICouponsState> {
|
|
static contextType = DataContext;
|
|
context!: React.ContextType<typeof DataContext>;
|
|
|
|
state: ICouponsState = {
|
|
coupons: []
|
|
};
|
|
|
|
componentDidMount() {
|
|
this.findNewElements();
|
|
window.addEventListener('buyform:rerender', () => this.findNewElements());
|
|
|
|
}
|
|
componentWillUnmount() {
|
|
window.removeEventListener('buyform:rerender', () => this.findNewElements());
|
|
}
|
|
|
|
findNewElements(): void {
|
|
const coupons: ICouponItem[] = []
|
|
const couponElements = document.querySelectorAll(couponsWrapperSelector) as NodeListOf<HTMLElement>;
|
|
couponElements.forEach((element: HTMLElement) => {
|
|
const buttonElem = element.querySelector('a');
|
|
const code = element.dataset.productAddCouponToCart ?? '';
|
|
|
|
if(!code || !buttonElem) {
|
|
return;
|
|
}
|
|
|
|
coupons.push({
|
|
buttonElem,
|
|
code,
|
|
state: ICouponState.None,
|
|
discountTemplateVersion: parseInt(element.dataset.discountTemplateVersion, 10) ?? 1,
|
|
});
|
|
ShopUtils.clearElement(buttonElem);
|
|
buttonElem.setAttribute('data-live-ignore', '1');
|
|
buttonElem.removeAttribute('href');
|
|
});
|
|
|
|
this.setState({coupons})
|
|
}
|
|
|
|
async copyCodeToClipboard(event: MouseEvent, code: string) {
|
|
event.preventDefault();
|
|
await navigator.clipboard.writeText(code);
|
|
this.successToast(_t('codeCopiedToClipboard'), event.target as HTMLElement);
|
|
}
|
|
|
|
|
|
render() {
|
|
if (this.context.loading) {
|
|
return '';
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{this.state.coupons.map((coupon: ICouponItem, i: number) => {
|
|
let component;
|
|
|
|
if (coupon.discountTemplateVersion === 2) {
|
|
let icon = 'Copy';
|
|
|
|
if (coupon.state == ICouponState.Loading) {
|
|
icon = 'Spinner';
|
|
} else if (coupon.state == ICouponState.Failed) {
|
|
icon = 'CircleX';
|
|
} else if (this.isCodeActive(coupon.code)) {
|
|
coupon.buttonElem.classList.add('active');
|
|
icon = 'CheckCircle';
|
|
}
|
|
|
|
component = (
|
|
<>
|
|
<span onClick={() => this.activateCoupon(coupon, true)}>{coupon.code}</span>
|
|
<Icon
|
|
icon={icon as IconType}
|
|
className={`c-icon ${coupon.state === ICouponState.Loading ? 'loading' : ''}`}
|
|
onClick={(event) => {
|
|
if (coupon.state === ICouponState.None && !this.isCodeActive(coupon.code)) {
|
|
void this.copyCodeToClipboard(event, coupon.code);
|
|
}
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
} else {
|
|
let statusClass = '';
|
|
|
|
if (coupon.state == ICouponState.Loading) {
|
|
statusClass = 'coupon-loading';
|
|
} else if (coupon.state == ICouponState.Failed) {
|
|
statusClass = 'coupon-failed';
|
|
} else if (this.isCodeActive(coupon.code)) {
|
|
statusClass = 'coupon-added';
|
|
}
|
|
|
|
component = (<span onClick={() => this.activateCoupon(coupon)} className={statusClass}>{_t('aktivovat')}</span>);
|
|
}
|
|
|
|
return ReactDOM.createPortal(
|
|
component,
|
|
coupon.buttonElem
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
}
|
|
|
|
protected activateCoupon = (coupon: ICouponItem, showSuccessToast = false) => {
|
|
if (coupon.state == ICouponState.Loading || this.isCodeActive(coupon.code)) {
|
|
return;
|
|
}
|
|
this.updateCouponState(coupon, ICouponState.Loading);
|
|
|
|
const variables = { code: coupon.code };
|
|
this.context.client.mutate({ mutation: ADD_CART_COUPON, variables})
|
|
.then((result) => {
|
|
if (result.data.addCoupon.result.isSuccess) {
|
|
|
|
this.updateCouponState(coupon, ICouponState.None);
|
|
this.context.updateShopData(
|
|
{ cart: ShopUtils.prepareCartData(result.data.addCoupon.cart) } as IData
|
|
);
|
|
if (showSuccessToast) {
|
|
this.successToast(_t('codeAddedSuccessfully'), coupon.buttonElem);
|
|
}
|
|
} else {
|
|
this.updateCouponState(coupon, ICouponState.Failed, result.data.addCoupon.result?.message);
|
|
this.errorToast(coupon);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
this.updateCouponState(coupon, ICouponState.Failed);
|
|
ShopUtils.handleFetchError(error, {query: 'ADD_CART_COUPON', variables});
|
|
});
|
|
};
|
|
|
|
protected successToast(message: string, referenceElement: HTMLElement) {
|
|
this.context.addToast({ content: message, type: ToastType.Computed, referenceElement, status: ToastStatus.Success });
|
|
}
|
|
|
|
protected errorToast(coupon: ICouponItem) {
|
|
this.context.addToast({content: coupon.message, type:ToastType.Computed, referenceElement: coupon.buttonElem, status:ToastStatus.Error});
|
|
}
|
|
|
|
protected isCodeActive(code: string): boolean {
|
|
return !!this.context.cart.activeCoupons.find((c) => c.code == code);
|
|
}
|
|
|
|
protected updateCouponState(coupon: ICouponItem, state: ICouponState, message?: string) {
|
|
const newCouponState = this.state.coupons.map((c) => {
|
|
if (c.code == coupon.code) {
|
|
c.state = state;
|
|
c.message = message;
|
|
}
|
|
return c;
|
|
});
|
|
|
|
this.setState({ coupons: newCouponState } as ICouponsState);
|
|
}
|
|
|
|
}
|