Skip to content

Commit

Permalink
feat(PAYMENTS-19008) rewrite sdk to lit
Browse files Browse the repository at this point in the history
  • Loading branch information
ekireevxs committed Apr 22, 2024
1 parent a755bce commit a6b5276
Show file tree
Hide file tree
Showing 15 changed files with 260 additions and 227 deletions.
51 changes: 49 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"dependencies": {
"currency-format": "^1.0.13",
"i18next": "^23.4.6",
"lit": "^3.1.3",
"tsyringe": "^4.8.0"
}
}
4 changes: 4 additions & 0 deletions src/core/form/form-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export class FormLoader {
}

public setFieldLoaded(name: string): void {
if (!this._fields) {
return;
}

if (name in this._fields) {
this._fields[name] = true;
}
Expand Down
77 changes: 77 additions & 0 deletions src/core/web-components/lit-web-component.abstract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { FormLoader } from '../form/form-loader';
import { container } from 'tsyringe';
import { createFinishLoadingEvent } from '../../shared/loading-state/dispatch-finish-loading-event.function';
import { isLoadingCssClassName } from '../../shared/loading-state/is-loading-css-class-name.const';
import { html, LitElement, TemplateResult } from 'lit';

export abstract class LitWebComponentAbstract extends LitElement {
protected formLoader: FormLoader = container.resolve(FormLoader);

protected eventListeners: Array<{
element: Element;
eventType: string;
listener(event: Event): void;
}> = [];

public disconnectedCallback(): void {
super.disconnectedCallback();
this.removeAllEventListeners();
}

public createRenderRoot(): LitElement {
return this;
}

protected abstract render(): void;

protected renderByCondition(
condition: boolean,
template: TemplateResult<1>,
defaultValue: TemplateResult<1> = html``,
): TemplateResult<1> {
return condition ? template : defaultValue;
}

protected addEventListenerToElement(
element: Element,
eventType: string,
listener: (event: Event) => void,
): void {
element.addEventListener(eventType, listener);
this.eventListeners.push({ element, eventType, listener });
}

protected removeAllEventListeners(): void {
this.eventListeners.forEach((item) => {
item.element.removeEventListener(item.eventType, item.listener);
});
this.eventListeners = [];
}

protected getJsonOrNull(data: string): unknown | null {
try {
return JSON.parse(data);
} catch (err: unknown) {
return null;
}
}

protected finishLoadingFormControlHandler(componentName: string): void {
this.formLoader.setFieldLoaded(componentName);
}

protected startLoadingComponentHandler(): void {
this.classList.add(isLoadingCssClassName);
}

protected finishLoadingComponentHandler(componentName: string): void {
this.classList.remove(isLoadingCssClassName);
this.dispatchFinishLoadingEvent(componentName);
}

protected dispatchFinishLoadingEvent(componentName: string): void {
this.dispatchEvent(createFinishLoadingEvent(componentName));
this.formLoader.setFieldLoaded(componentName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { headlessCheckoutAppUrl } from '../../../features/headless-checkout/environment';
import { LitWebComponentAbstract } from '../lit-web-component.abstract';
import { property } from 'lit/decorators.js';

export abstract class LitSecureComponentAbstract extends LitWebComponentAbstract {
@property({ attribute: false })
protected componentName: string | null = null;

protected getSecureHtml(): string {
if (!this.componentName) {
throw new Error('Component name is required');
}

return `<iframe src='${headlessCheckoutAppUrl}/secure-components/${this.componentName}'></iframe>`;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
psdk-card-number {
psdk-card-number, psdk-lit-card-number {
.wrapper {
position: relative;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import { EventName } from '../../../../core/event-name.enum';

import { getCardNumberComponentTemplate } from './card-number.template';
import { updateCreditCardTypeHandler } from '../../post-messages-handlers/update-credit-card-type.handler';
import { cardIconsMap } from './card-icons.map';
import { CardType } from './card-type.enum';
import { CardNumberComponentAttributes } from './card-number-component-attributes.enum';
import { TextComponent } from '../text-component/text.component';
import './card-number.component.scss';
import { property, customElement } from 'lit/decorators.js';
import { TextComponent } from '../text-component/text.component';
import { html, TemplateResult } from 'lit';

@customElement('psdk-card-number')
export class CardNumberComponent extends TextComponent {
@property({ attribute: false })
private cardType = 'default';

private isCardIconShown = true;

public static get observedAttributes(): string[] {
return [
CardNumberComponentAttributes.name,
CardNumberComponentAttributes.icon,
];
}
@property({ type: Boolean, attribute: CardNumberComponentAttributes.icon })
private readonly isCardIconShown = false;

protected connectedCallback(): void {
public connectedCallback(): void {
super.connectedCallback();

this.postMessagesClient.listen<{ cardType: string }>(
Expand All @@ -29,65 +25,32 @@ export class CardNumberComponent extends TextComponent {
(res) => {
if (this.isCardIconShown && this.cardType !== res?.cardType) {
this.cardType = res?.cardType ? res.cardType : 'default';
this.updateCardIcon(this.cardType);
}
},
);
}

protected attributeChangedCallback(): void {
if (!this.formSpy.formWasInit) {
this.formSpy.listenFormInit(() => {
super.attributeChangedCallback();
this.toggleCardIconVisibility();
});
return;
}
}

protected getHtml(): string {
const secureHtml = this.getSecureHtml();
return getCardNumberComponentTemplate({
title: this.config?.title,
error: this.config?.error,
isCardIconShown: this.isCardIconShown,
secureHtml,
});
}

private updateCardIcon(iconName: string): void {
const rootElement = this.shadowRoot ?? this;

const iconWrapperElement = rootElement.querySelector('.card-icon');

const iconElement = iconWrapperElement!.querySelector(
'.icon',
) as HTMLImageElement | null;

const cardIcon =
cardIconsMap[iconName as CardType] ?? cardIconsMap[CardType.DEFAULT_CARD];
if (!iconElement) {
const newIconElement = this.window.document.createElement('img');
newIconElement.width = 24;
newIconElement.height = 18;
newIconElement.classList.add('icon');
newIconElement.src = cardIcon;
iconWrapperElement!.appendChild(newIconElement);
} else {
iconElement.src = cardIcon;
protected render(): TemplateResult<1> {
if (!this.formSpy.formWasInit || !this.componentName) {
return html``;
}
}

private toggleCardIconVisibility(): void {
const isCardIconShownAttr = this.getAttribute(
CardNumberComponentAttributes.icon,
const textComponentTemplate = this.getTextComponentTemplate(
this.getCardIconTemplate(),
);

if (!isCardIconShownAttr) {
this.isCardIconShown = true;
return;
}
return html`${textComponentTemplate}`;
}

this.isCardIconShown = isCardIconShownAttr === 'true';
private getCardIconTemplate(): TemplateResult<1> | null {
const cardIcon =
cardIconsMap[this.cardType as CardType] ??
cardIconsMap[CardType.DEFAULT_CARD];

return this.isCardIconShown
? html`<span class="card-icon">
<img width="24" height="18" class="icon" src="${cardIcon}" />
</span>`
: null;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { PhoneComponentAttributes } from './phone-component-attributes.enum';
import { headlessCheckoutAppUrl } from '../../environment';
import { TextComponent } from '../text-component/text.component';
import { property, customElement } from 'lit/decorators.js';

@customElement('psdk-phone')
export class PhoneComponent extends TextComponent {
public static get observedAttributes(): string[] {
return [PhoneComponentAttributes.showFlags];
}
@property({ attribute: PhoneComponentAttributes.name })
protected inputName = 'phone';

protected connectedCallback(): void {
super.connectedCallback();
if (!this.getAttribute(PhoneComponentAttributes.name)) {
this.setAttribute(PhoneComponentAttributes.name, 'phone');
}
}
@property({ type: Boolean, attribute: PhoneComponentAttributes.showFlags })
private readonly showFlags = false;

protected getSecureHtml(): string {
if (!this.componentName) {
throw new Error('Component name is required');
}

let src = `${headlessCheckoutAppUrl}/secure-components/${this.componentName}`;
const showFlags = this.getAttribute(PhoneComponentAttributes.showFlags);

if (showFlags) {
if (this.showFlags) {
src += '?showFlags=true';
}
return `<iframe src='${src}'></iframe>`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ControlComponentConfig } from '../control-component-config.interface';
import { TextConfigTooltip } from './text-config-tooltip.interface';
import { TemplateResult } from 'lit';

export interface TextComponentConfig extends ControlComponentConfig {
secureHtml: string;
additionalControls?: string;
additionalControls?: TemplateResult<1>;
tooltip?: TextConfigTooltip;
}
Loading

0 comments on commit a6b5276

Please sign in to comment.