@apollo/client#useSubscription TypeScript Examples

The following examples show how to use @apollo/client#useSubscription. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Simulator.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
Simulator: React.FC<SimulatorProps> = ({
  showSimulator,
  setSimulatorId,
  simulatorIcon = true,
  message,
  flowSimulator,
  isPreviewMessage,
  resetMessage,
  getFlowKeyword,
  interactiveMessage,
  showHeader = true,
  hasResetButton = false,
}: SimulatorProps) => {
  const [inputMessage, setInputMessage] = useState('');
  const [simulatedMessages, setSimulatedMessage] = useState<any>();
  const [isOpen, setIsOpen] = useState(false);

  // Template listing
  const [isDrawerOpen, setIsDrawerOpen] = useState<Boolean>(false);
  const [selectedListTemplate, setSelectedListTemplate] = useState<any>(null);

  const variables = { organizationId: getUserSession('organizationId') };

  let messages: any[] = [];
  let simulatorId = '';
  const sender = {
    name: '',
    phone: '',
  };
  // chat messages will be shown on simulator
  const isSimulatedMessage = true;

  const sendMessage = (senderDetails: any, interactivePayload?: any, templateValue?: any) => {
    const sendMessageText = inputMessage === '' && message ? message : inputMessage;

    // check if send message text is not empty
    if (!sendMessageText && !interactivePayload && !templateValue) return;

    let type = 'text';

    let payload: any = {};

    let context: any = {};

    if (interactivePayload) {
      type = interactivePayload.payload.type;
      payload = interactivePayload.payload;
      delete payload.type;
      context = interactivePayload.context;
    } else if (templateValue) {
      payload.text = templateValue;
    } else {
      payload.text = sendMessageText;
    }

    axios
      .post(GUPSHUP_CALLBACK_URL, {
        type: 'message',
        payload: {
          id: uuidv4(),
          type,
          payload,
          sender: senderDetails,
          context,
        },
      })
      .catch((error) => {
        // add log's
        setLogs(
          `sendMessageText:${sendMessageText} GUPSHUP_CALLBACK_URL:${GUPSHUP_CALLBACK_URL}`,
          'info'
        );
        setLogs(error, 'error');
      });
    setInputMessage('');
    // reset the message from floweditor for the next time
    if (resetMessage) {
      resetMessage();
    }
  };

  const [loadSimulator, { data: allConversations, subscribeToMore }] = useLazyQuery(
    SIMULATOR_SEARCH_QUERY,
    {
      fetchPolicy: 'network-only',
      nextFetchPolicy: 'cache-only',
      onCompleted: ({ search }) => {
        if (subscribeToMore) {
          const subscriptionVariables = { organizationId: getUserSession('organizationId') };
          // message received subscription
          subscribeToMore({
            document: SIMULATOR_MESSAGE_RECEIVED_SUBSCRIPTION,
            variables: subscriptionVariables,
            updateQuery: (prev, { subscriptionData }) =>
              updateSimulatorConversations(prev, subscriptionData, 'RECEIVED'),
          });

          // message sent subscription
          subscribeToMore({
            document: SIMULATOR_MESSAGE_SENT_SUBSCRIPTION,
            variables: subscriptionVariables,
            updateQuery: (prev, { subscriptionData }) =>
              updateSimulatorConversations(prev, subscriptionData, 'SENT'),
          });

          if (search.length > 0) {
            sendMessage({ name: search[0].contact.name, phone: search[0].contact.phone });
          }
        }
      },
    }
  );

  const { data: simulatorSubscribe }: any = useSubscription(SIMULATOR_RELEASE_SUBSCRIPTION, {
    variables,
  });

  useEffect(() => {
    if (simulatorSubscribe) {
      try {
        const userId = JSON.parse(simulatorSubscribe.simulatorRelease).simulator_release.user_id;
        if (userId.toString() === getUserSession('id')) {
          setSimulatorId(0);
        }
      } catch (error) {
        setLogs('simulator release error', 'error');
      }
    }
  }, [simulatorSubscribe]);

  const [getSimulator, { data }] = useLazyQuery(GET_SIMULATOR, {
    fetchPolicy: 'network-only',
    onCompleted: (simulatorData) => {
      if (simulatorData.simulatorGet) {
        loadSimulator({ variables: getSimulatorVariables(simulatorData.simulatorGet.id) });
        setSimulatorId(simulatorData.simulatorGet.id);
      } else {
        setNotification(
          'Sorry! Simulators are in use by other staff members right now. Please wait for it to be idle',
          'warning'
        );
      }
    },
  });

  const [releaseSimulator]: any = useLazyQuery(RELEASE_SIMULATOR, {
    fetchPolicy: 'network-only',
  });

  if (allConversations && data && data.simulatorGet) {
    // currently setting the simulated contact as the default receiver
    const simulatedContact = allConversations.search.filter(
      (item: any) => item.contact.id === data.simulatorGet.id
    );
    if (simulatedContact.length > 0) {
      messages = simulatedContact[0].messages;
      simulatorId = simulatedContact[0].contact.id;
      sender.name = simulatedContact[0].contact.name;
      sender.phone = simulatedContact[0].contact.phone;
    }
  }

  const getStyleForDirection = (direction: string, isInteractive: boolean): string => {
    const simulatorClasses = [styles.ReceivedMessage, styles.InteractiveReceivedMessage];
    if (isInteractive && direction === 'received') {
      return simulatorClasses.join(' ');
    }

    if (direction === 'send') {
      return styles.SendMessage;
    }

    return styles.ReceivedMessage;
  };

  const releaseUserSimulator = () => {
    releaseSimulator();
    setSimulatorId(0);
  };

  const handleOpenListReplyDrawer = (items: any) => {
    setSelectedListTemplate(items);
    setIsDrawerOpen(true);
  };

  const sendMediaMessage = (type: string, payload: any) => {
    axios
      .post(GUPSHUP_CALLBACK_URL, {
        type: 'message',
        payload: {
          id: uuidv4(),
          type,
          payload,
          sender: {
            // this number will be the simulated contact number
            phone: data ? data.simulatorGet?.phone : '',
            name: data ? data.simulatorGet?.name : '',
          },
        },
      })
      .catch((error) => {
        // add log's
        setLogs(`sendMediaMessage:${type} GUPSHUP_CALLBACK_URL:${GUPSHUP_CALLBACK_URL}`, 'info');
        setLogs(error, 'error');
      });
  };

  const renderMessage = (
    messageObject: any,
    direction: string,
    index: number,
    isInteractive: boolean = false
  ) => {
    const { insertedAt, type, media, location, interactiveContent, bspMessageId, templateType } =
      messageObject;

    const messageType = isInteractive ? templateType : type;
    const { body, buttons } = WhatsAppTemplateButton(isInteractive ? '' : messageObject.body);

    // Checking if interactive content is present then only parse to JSON
    const content = interactiveContent && JSON.parse(interactiveContent);
    let isInteractiveContentPresent = false;
    let template;

    if (content) {
      isInteractiveContentPresent = !!Object.entries(content).length;

      if (isInteractiveContentPresent && messageType === INTERACTIVE_LIST) {
        template = (
          <>
            <ListReplyTemplate
              {...content}
              bspMessageId={bspMessageId}
              showHeader={showHeader}
              component={SimulatorTemplate}
              onGlobalButtonClick={handleOpenListReplyDrawer}
            />
            <TimeComponent direction={direction} insertedAt={insertedAt} />
          </>
        );
      }

      if (isInteractiveContentPresent && messageType === INTERACTIVE_QUICK_REPLY) {
        template = (
          <QuickReplyTemplate
            {...content}
            isSimulator
            showHeader={showHeader}
            disabled={isInteractive}
            bspMessageId={bspMessageId}
            onQuickReplyClick={(value: any) => sendMessage(sender, value)}
          />
        );
      }
    }

    return (
      <div key={index}>
        <div className={getStyleForDirection(direction, isInteractiveContentPresent)}>
          {isInteractiveContentPresent && direction !== 'send' ? (
            template
          ) : (
            <>
              <ChatMessageType
                type={messageType}
                media={media}
                body={body}
                location={location}
                isSimulatedMessage={isSimulatedMessage}
              />
              <TimeComponent direction={direction} insertedAt={insertedAt} />
            </>
          )}
        </div>
        <div className={styles.TemplateButtons}>
          <TemplateButtons
            template={buttons}
            callbackTemplateButtonClick={(value: string) => sendMessage(sender, null, value)}
            isSimulator
          />
        </div>
      </div>
    );
  };

  const getChatMessage = () => {
    const chatMessage = messages
      .map((simulatorMessage: any, index: number) => {
        if (simulatorMessage.receiver.id === simulatorId) {
          return renderMessage(simulatorMessage, 'received', index);
        }
        return renderMessage(simulatorMessage, 'send', index);
      })
      .reverse();
    setSimulatedMessage(chatMessage);
  };

  const getPreviewMessage = () => {
    if (message && message.type) {
      const previewMessage = renderMessage(message, 'received', 0);
      if (['STICKER', 'AUDIO'].includes(message.type)) {
        setSimulatedMessage(previewMessage);
      } else if (message.body || message.media?.caption) {
        setSimulatedMessage(previewMessage);
      } else {
        // To get rid of empty body and media caption for preview HSM
        setSimulatedMessage('');
      }
    }

    if (interactiveMessage) {
      const { templateType, interactiveContent } = interactiveMessage;
      const previewMessage = renderMessage(interactiveMessage, 'received', 0, true);
      setSimulatedMessage(previewMessage);
      if (templateType === INTERACTIVE_LIST) {
        const { items } = JSON.parse(interactiveContent);
        setSelectedListTemplate(items);
      } else {
        setIsDrawerOpen(false);
      }
    }
  };

  // to display only preview for template
  useEffect(() => {
    if (isPreviewMessage) {
      getPreviewMessage();
    }
  }, [message]);

  // for loading conversation
  useEffect(() => {
    if (allConversations && data) {
      getChatMessage();
    }
  }, [data, allConversations]);

  // for sending message to Gupshup
  useEffect(() => {
    if (!isPreviewMessage && message && data) {
      sendMessage(sender);
    }
  }, [message]);

  useEffect(() => {
    if (isPreviewMessage && interactiveMessage) {
      getPreviewMessage();
    }

    // Cleaning up simulator when switching between templates
    if (!interactiveMessage) {
      setSimulatedMessage(null);
      setIsDrawerOpen(false);
    }
  }, [interactiveMessage]);

  const messageRef = useCallback(
    (node: any) => {
      if (node) {
        const nodeCopy = node;
        setTimeout(() => {
          nodeCopy.scrollTop = node.scrollHeight;
        }, 100);
      }
    },
    [messages]
  );

  const handleAttachmentClick = (media: any) => {
    const { name: type, payload } = media;

    const mediaUrl = document.querySelector('#media');
    if (mediaUrl) {
      const url = mediaUrl.getAttribute('data-url');
      if (url) {
        payload.url = url;
      }
    }
    sendMediaMessage(type, payload);
    setIsOpen(false);
  };

  const handleListReplyDrawerClose = () => {
    setIsDrawerOpen(false);
    setSelectedListTemplate(null);
  };

  const handleListDrawerItemClick = (payloadObject: any) => {
    sendMessage(sender, payloadObject);
    handleListReplyDrawerClose();
  };

  const dropdown = (
    <ClickAwayListener onClickAway={() => setIsOpen(false)}>
      <div className={styles.Dropdown} id="media">
        {SAMPLE_MEDIA_FOR_SIMULATOR.map((media: any) => (
          <Button
            onClick={() => handleAttachmentClick(media)}
            key={media.id}
            className={styles.AttachmentOptions}
          >
            <MessageType type={media.id} color="dark" />
          </Button>
        ))}
      </div>
    </ClickAwayListener>
  );

  const simulator = (
    <Draggable>
      <div className={styles.SimContainer}>
        <div>
          <div id="simulator" className={styles.Simulator}>
            {!isPreviewMessage && (
              <>
                <ClearIcon
                  className={styles.ClearIcon}
                  onClick={() => {
                    releaseUserSimulator();
                  }}
                  data-testid="clearIcon"
                />
                {hasResetButton && (
                  <ResetIcon
                    data-testid="resetIcon"
                    className={styles.ResetIcon}
                    onClick={() => {
                      if (getFlowKeyword) {
                        getFlowKeyword();
                      }
                    }}
                  />
                )}
              </>
            )}

            <div className={styles.Screen}>
              <div className={styles.Header}>
                <ArrowBackIcon />
                <img src={DefaultWhatsappImage} alt="default" />
                <span data-testid="beneficiaryName">Beneficiary</span>
                <div>
                  <VideocamIcon />
                  <CallIcon />
                  <MoreVertIcon />
                </div>
              </div>
              <div className={styles.Messages} ref={messageRef} data-testid="simulatedMessages">
                {simulatedMessages}
              </div>
              {isDrawerOpen && <div className={styles.BackgroundTint} />}
              <div className={styles.Controls}>
                <div>
                  <InsertEmoticonIcon className={styles.Icon} />
                  <input
                    type="text"
                    data-testid="simulatorInput"
                    onKeyPress={(event: any) => {
                      if (event.key === 'Enter') {
                        sendMessage(sender);
                      }
                    }}
                    value={inputMessage}
                    placeholder="Type a message"
                    disabled={isPreviewMessage}
                    onChange={(event) => setInputMessage(event.target.value)}
                  />
                  <AttachFileIcon
                    data-testid="attachment"
                    className={styles.AttachFileIcon}
                    onClick={() => setIsOpen(!isOpen)}
                  />
                  {isOpen ? dropdown : null}
                  <CameraAltIcon className={styles.Icon} />
                </div>

                <Button
                  variant="contained"
                  color="primary"
                  className={styles.SendButton}
                  disabled={isPreviewMessage}
                  onClick={() => sendMessage(sender)}
                >
                  <MicIcon />
                </Button>
              </div>
              {isDrawerOpen && (
                <ListReplyTemplateDrawer
                  drawerTitle="Items"
                  items={selectedListTemplate}
                  disableSend={!!interactiveMessage}
                  onItemClick={handleListDrawerItemClick}
                  onDrawerClose={handleListReplyDrawerClose}
                />
              )}
            </div>
          </div>
        </div>
      </div>
    </Draggable>
  );

  const handleSimulator = () => {
    // check for the flowkeyword from floweditor
    if (getFlowKeyword) {
      getFlowKeyword();
    }
    getSimulator();
  };
  return (
    <>
      {showSimulator && simulator}
      {simulatorIcon && (
        <SimulatorIcon
          data-testid="simulatorIcon"
          className={showSimulator ? styles.SimulatorIconClicked : styles.SimulatorIconNormal}
          onClick={() => {
            if (showSimulator) {
              releaseUserSimulator();
            } else {
              handleSimulator();
            }
          }}
        />
      )}

      {flowSimulator && (
        <div className={styles.PreviewButton}>
          <FormButton
            variant="outlined"
            color="primary"
            data-testid="previewButton"
            className={styles.Button}
            onClick={() => {
              if (showSimulator) {
                releaseUserSimulator();
              } else {
                handleSimulator();
              }
            }}
          >
            Preview
            {showSimulator && <CancelOutlinedIcon className={styles.CrossIcon} />}
          </FormButton>
        </div>
      )}
    </>
  );
}
Example #2
Source File: SavedSearchToolbar.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
SavedSearchToolbar: React.SFC<SavedSearchToolbarProps> = (props) => {
  const { searchMode, refetchData, savedSearchCriteriaCallback, onSelect } = props;
  const [selectedSavedSearch, setSelectedSavedSearch] = useState<number | null>(null);
  const [optionsSelected, setOptionsSelected] = useState(false);
  const [fixedSearches, setFixedSearches] = useState<any>([]);
  const [searchesCount, setSearchesCount] = useState<any>({});
  const [anchorEl, setAnchorEl] = useState(null);
  const Ref = useRef(null);
  const open = Boolean(anchorEl);
  const variables = { organizationId: getUserSession('organizationId') };

  const { data: collectionCount } = useSubscription(COLLECTION_COUNT_SUBSCRIPTION, { variables });

  const { data: countData } = useQuery<any>(SEARCHES_COUNT, {
    variables,
  });

  useEffect(() => {
    if (countData) {
      const collectionStats = JSON.parse(countData.collectionStats);
      if (collectionStats[variables.organizationId]) {
        setSearchesCount(collectionStats[variables.organizationId]);
      }
    }
  }, [countData]);

  useEffect(() => {
    if (collectionCount) {
      const countDataSubscription = JSON.parse(collectionCount.collectionCount);
      setSearchesCount(countDataSubscription.collection);
    }
  }, [collectionCount]);

  // default query variables
  const queryVariables = {
    filter: { isReserved: true },
    opts: {},
  };

  // remove selected searches on search
  if (searchMode && selectedSavedSearch) {
    setSelectedSavedSearch(null);
  }

  const { loading, error, refetch } = useQuery<any>(SAVED_SEARCH_QUERY, {
    variables: queryVariables,
    onCompleted: (data) => {
      setFixedSearches(data.savedSearches);
    },
  });

  const handlerSavedSearchCriteria = (
    savedSearchCriteria: string | null,
    savedSearchId: number | null
  ) => {
    savedSearchCriteriaCallback(savedSearchCriteria, savedSearchId);
    setSelectedSavedSearch(savedSearchId);
  };

  const handleAdditionalSavedSearch = (search: any) => {
    const replaceSearchIndex = fixedSearches
      .map((savedSearch: any) => savedSearch.id)
      .indexOf(search.id);
    const fixedSearchesCopy = JSON.parse(JSON.stringify(fixedSearches));
    if (replaceSearchIndex !== -1) {
      [fixedSearchesCopy[replaceSearchIndex], fixedSearchesCopy[2]] = [
        fixedSearches[2],
        fixedSearches[replaceSearchIndex],
      ];
      setFixedSearches(fixedSearchesCopy);
    }
    handlerSavedSearchCriteria(search.args, search.id);
  };

  useEffect(() => {
    // display created searches
    if (refetchData.savedSearches) {
      refetch();
      handleAdditionalSavedSearch(refetchData.savedSearches);
    }
  }, [refetchData.savedSearches]);

  if (loading) return <Loading />;
  if (error) {
    setErrorMessage(error);
    return <div>error</div>;
  }

  const savedSearchList = fixedSearches.slice(0, 3).map((savedSearch: any) => {
    // set the selected class if the button is clicked
    const labelClass = [styles.SavedSearchItemLabel];
    const countClass = [styles.SavedSearchCount];
    if (savedSearch.id === selectedSavedSearch) {
      labelClass.push(styles.SavedSearchItemSelected);
      countClass.push(styles.SavedSearchSelectedCount);
    }

    const count = searchesCount[savedSearch.shortcode] ? searchesCount[savedSearch.shortcode] : 0;
    return (
      <div
        data-testid="savedSearchDiv"
        className={styles.SavedSearchItem}
        key={savedSearch.id}
        onClick={() => {
          handlerSavedSearchCriteria(savedSearch.args, savedSearch.id);
          onSelect();
        }}
        onKeyDown={() => {
          handlerSavedSearchCriteria(savedSearch.args, savedSearch.id);
          onSelect();
        }}
        aria-hidden="true"
      >
        <div className={labelClass.join(' ')}>{savedSearch.shortcode}</div>
        <Tooltip title={count} placement="right">
          <div className={countClass.join(' ')}>{numberToAbbreviation(count)}</div>
        </Tooltip>
      </div>
    );
  });

  const handleClickAway = () => {
    setAnchorEl(null);
    setOptionsSelected(false);
  };

  const additionalOptions = (
    <Popper
      open={open}
      anchorEl={anchorEl}
      placement="bottom"
      transition
      className={styles.PopperContainer}
    >
      {({ TransitionProps }) => (
        <Fade {...TransitionProps} timeout={350}>
          <Paper elevation={3} className={styles.Popper}>
            {fixedSearches.slice(3, 6).map((search: any) => {
              const count = searchesCount[search.shortcode] ? searchesCount[search.shortcode] : 0;
              return (
                <div
                  key={search.id}
                  className={styles.LabelContainer}
                  onClick={() => handleAdditionalSavedSearch(search)}
                  aria-hidden="true"
                >
                  <span className={styles.Label}>{search.shortcode}</span>
                  <span className={styles.Count}>{numberToAbbreviation(count)}</span>
                </div>
              );
            })}
          </Paper>
        </Fade>
      )}
    </Popper>
  );

  return (
    <div className={styles.SavedSearchToolbar}>
      <div className={styles.SaveSearchContainer}>{savedSearchList}</div>
      <div className={styles.MoreLink}>
        <ClickAwayListener onClickAway={handleClickAway}>
          <IconButton
            onClick={() => {
              setAnchorEl(Ref.current);
              setOptionsSelected(true);
            }}
            aria-label="more"
            aria-controls="long-menu"
            aria-haspopup="true"
            size="small"
            ref={Ref}
          >
            {optionsSelected ? (
              <OptionsIconSelected className={styles.OptionsIcon} />
            ) : (
              <OptionsIcon className={styles.OptionsIcon} />
            )}
          </IconButton>
        </ClickAwayListener>
        {additionalOptions}
      </div>
    </div>
  );
}