Untitled

 avatar
unknown
plain_text
11 days ago
7.9 kB
3
Indexable
import RestClient from '@/util/api';
import '@/core/api';
import { RestClientError } from '@/core/RestClientError';
import ScId from '@/onCommerce/sc-common/ScId';
import { Trans, useTranslation } from 'react-i18next';
import useGlobalNotification from '@/core/GlobalNotification/useGlobalNotification';
import { QueryKey, useQueryClient } from 'react-query';
import { TFunction } from 'i18next';
import { Client, Message, Subscription } from 'webstomp-client';
import { useClient } from '@/portal/views/ClientProvider';
import { useEffect } from 'react';
import TextLink from '@/core/components/TextLink/TextLink';
import { GlobalNotificationContextState } from '@/core/GlobalNotification/GlobalNotificationContext';

/**
 * Convenience wrapper around the RestClient in order to simplify read and write operations in screen components.
 */
export default class ScBackend {
  /**
   * Wrapper so send a read operation in screen components. The base part for the URL is already set here 'api/uib/onCommerce/'
   * @param url The url of the operation without slash at the beginning e.g. 'basket/data'
   * @param params Any request params to append to the URL (optional; disallows url-embedding; converts via Object.entries() with basic support for non-nested arrays).
   * @param scId The screen component ID.
   */
  public static read(url: string, scId: ScId, params?: object): any {
    if (params != null) {
      url += `?${this.buildQueryParams(params)}`;
    }
    return RestClient.get(`./api/uib/onCommerce/${url}`, null, {
      headers: {
        'Component-Id': scId.toRequestHeaderValue(),
      },
    }).catch(err => {
      throw new RestClientError(err, err.status);
    });
  }

  private static buildQueryParams(parameterObject: object): URLSearchParams {
    const params = new URLSearchParams();
    Object.entries(parameterObject).forEach(([key, value]) => {
      if (Array.isArray(value)) {
        value.forEach(v => params.append(key, this.encodeQueryParam(v)));
      } else if (value != null) {
        params.append(key, this.encodeQueryParam(value));
      }
    });
    return params;
  }

  private static encodeQueryParam(param: unknown): string {
    if (
      typeof param === 'string' ||
      typeof param === 'number' ||
      typeof param === 'boolean'
    ) {
      return encodeURIComponent(param);
    } else {
      return encodeURIComponent(String(param));
    }
  }

  /**
   * Wrapper so send a write operation in screen components. The base part for the URL is already set here 'api/uib/onCommerce/'
   * @param url The url of the operation without slash at the beginning e.g. 'basket/data'
   * @param data The data of the operation.
   * @param scId The screen component ID.
   */
  public static write(url: string, data: any, scId: ScId): any {
    return RestClient.post(`./api/uib/onCommerce/${url}`, data, {
      headers: {
        'Component-Id': scId.toRequestHeaderValue(),
      },
    }).catch((error: any) => {
      throw new RestClientError(error);
    });
  }
}

/**
 * Error handler displaying a given error or default fallback message
 * @param eShopErrorPrefix Error message to be attached to displayed when no specific backend message
 */
export const handleErrorWithFallback = (
  t: TFunction,
  notification: GlobalNotificationContextState,
  eShopErrorPrefix?: string
) => {
  return (error: RestClientError) => {
    let message;
    const translationKey = error?.responseJSON?.translationKey;
    const translationData = error?.responseJSON?.translationData;

    if (
      (translationKey && !translationData) ||
      (translationKey &&
        translationData &&
        !Object.keys(translationData).length)
    ) {
      message = t(translationKey);
    } else if (
      translationKey &&
      translationData &&
      Object.keys(translationData).length
    ) {
      message = t(translationKey, {
        ...translationData,
      });
    } else if (eShopErrorPrefix) {
      message = (
        <>
          {`${eShopErrorPrefix} `}
          <Trans i18nKey="errorHandling:contactEshopSupport" t={t}>
            {' '}
            <TextLink
              text={'support-omnipluseshop-hq@daimlertruck.com'}
              url={`mailto:support-omnipluseshop-hq@daimlertruck.com`}
            />{' '}
          </Trans>
        </>
      );
    } else {
      message = t('errorHandling:system.failure', {
        systemName: 'Backend',
      });
    }

    notification.showNotification({
      message,
      severity: 'error',
    });
  };
};

/**
 * Error handler displaying a global notification for a given error
 * @param eShopErrorPrefix Error message to be attached to displayed when no specific backend message
 */
export const useHandleErrorWithFallback = (eShopErrorPrefix?: string) => {
  const { t } = useTranslation(['common', 'errorHandling', 'htmlMail']);
  const notification = useGlobalNotification();

  return handleErrorWithFallback(t, notification, eShopErrorPrefix);
};

/**
 * Default mutation handler overwriting the query data for the given QueryKey
 * with the new data received on success and displaying a success notification.
 */
export const useDefaultMutationOptions = <T,>(queryKey: QueryKey) => {
  const { t } = useTranslation(['common', 'errorHandling']);
  const notification = useGlobalNotification();
  const queryClient = useQueryClient();

  return {
    onError: handleErrorWithFallback(t, notification),
    onSuccess: (result: T) => {
      queryClient.setQueryData(queryKey, result);
      const message = t('common:editSuccess');
      notification.showNotification({
        message,
        severity: 'success',
      });
    },
  };
};

/**
 * Mutation handler displaying a notification only in case of an error.
 */
export const useSilentActionMutationOptions = () => {
  const { t } = useTranslation(['errorHandling']);
  const notification = useGlobalNotification();

  return {
    onError: handleErrorWithFallback(t, notification),
  };
};

/**
 * Mutation handler displaying a notification on error and success but not
 * overwriting the query cache.
 */
export const useActionMutationOptions = <T,>() => {
  const { t } = useTranslation(['common', 'errorHandling']);
  const notification = useGlobalNotification();

  return {
    onError: handleErrorWithFallback(t, notification),
    onSuccess: () => {
      const message = t('common:editSuccess');
      notification.showNotification({
        message,
        severity: 'success',
      });
    },
  };
};

interface WebsocketSignal {
  signal: string;
}

/**
 * Hook for subscribing to the standard refetch signal for an SC topic.
 */
export const useRefetchSubscription = (
  topic: SubscriptionTopic,
  action: () => void
) => {
  return useSubscription(topic, (message?: WebsocketSignal) => {
    if (message?.signal === 'refetchData') {
      action();
    }
  });
};

/**
 * Hook for creating a subscription on a given topic using the default websocket client.
 */
export const useSubscription = (
  topic: SubscriptionTopic,
  messageHandler: (message: any) => void
) => {
  const client = useClient();
  useEffect(() => {
    const subscription =
      client && createSubscription(client, topic, messageHandler);
    return () => {
      if (subscription) {
        subscription.unsubscribe();
      }
    };
  }, [client]);
};

/**
 * Helper method creating a subscription on a given client and topic
 */
function createSubscription(
  client: Client,
  topic: string,
  callback: (messageBody: any) => void
): Subscription {
  const fullTopic = `/user/queue/oncommerce/sc/${topic}`;
  return client.subscribe(fullTopic, (message: Message) => {
    callback(JSON.parse(message.body));
  });
}

export enum SubscriptionTopic {
  Basket = 'basket',
  CrossSellingProducts = 'crossSellingProducts',
  EShopHeader = 'eShopHeader',
  OrderHistoryDetails = 'orderHistoryDetails',
  TrackRemoteEvent = 'trackRemoteEvent',
}
Editor is loading...
Leave a Comment