Files
2025-08-02 16:30:27 +02:00

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);
}
}