\n \n {`[\n {\n \"text\": \"Sample plain answer text\",\n \"show_text\": \"New answer again for testing\"\n \"qlinks\": ['hello', 'test question'],\n \"category\": 'test answers',\n \"category_color\": \"#d3d3d3\",\n },\n {\n \"text\": \"Another sample answer text\",\n \"show_text\": \"Another answer for testing\"\n \"qlinks\": ['question link', 'show me another answer'],\n \"category\": 'test answers 2',\n \"category_color\": \"#167be7\",\n },\n]`}\n \n \n);\n\nconst ImportQuestionModal = ({ fileType, onClose, show }) => {\n const {\n answersAllowed,\n currentStep,\n importErrorMsg,\n importWarnMsg,\n similarAnswerWarnMsg,\n finalAnswerWarnMsg,\n isImportBtnDisabled,\n modalBtnLoading,\n parsedFileQuestions,\n duplicatesFound,\n duplicatesFoundInFile,\n handleCancelImport,\n handleEditAnswer,\n handleRemoveAnswer,\n handleRemoveFile,\n handleToggleAnswerEditor,\n handleStepChange,\n handleUploadFile,\n handleSubmitFile,\n handleClearData,\n handleCheckValidations,\n setShowImportTip,\n showImportTip,\n visibility,\n verifiedQuestions,\n verificationBtnLoading,\n runTour,\n stepIndex,\n onClickCallback,\n } = useImportQuestionModal({ fileType, onClose, show });\n\n const handleQuestionIsVerified = answer => {\n return (\n verifiedQuestions?.filter(verifiedAnswer => verifiedAnswer === answer)\n ?.length > 0\n );\n };\n\n const handleDuplicates = question => {\n const questionDetails =\n fileType === 'json' ? question.text : question.replace('\\r', '');\n return (\n duplicatesFound.filter(\n item => item?.question.text?.replace('\\r', '') === question.text\n )?.length ||\n duplicatesFoundInFile?.filter(\n item =>\n (fileType === 'json'\n ? item?.question.text?.replace('\\r', '')\n : item?.replace('\\r', '')) === questionDetails\n ).length\n );\n };\n\n const handleCheckQuestion = question => {\n return fileType === 'json' ? question.text : question;\n };\n\n const ImportAlertMessage = () => {\n return (\n <>\n {importErrorMsg ? (\n 0 ||\n verificationBtnLoading ||\n duplicatesFoundInFile.length > 0\n }\n value=\"Save as Draft\"\n variant=\"gray\"\n onClick={() => {\n handleSubmitFile(fileType);\n }}\n />\n )}\n >\n );\n };\n\n return (\n <>\n handleSubmitFile(fileType)}\n okText=\"Submit\"\n title={{modalTitle[fileType]} }\n okButtonProps={{\n disabled: isImportBtnDisabled || !parsedFileQuestions?.length,\n }}\n cancelText={modalBtnLoading ? 'Close' : 'Cancel'}\n confirmLoading={modalBtnLoading}\n destroyOnClose\n width=\"700px\"\n footer={\n \n {currentStep ? (\n \n ) : null}\n {getActionButtons()}
\n \n \n }\n >\n \n \n {steps.map((step, idx) => (\n \n ))}\n \n \n {steps[currentStep].content()}\n
\n \n \n >\n );\n};\n\nImportQuestionModal.propTypes = {\n fileType: PropTypes.string,\n onClose: PropTypes.func,\n show: PropTypes.bool.isRequired,\n};\n\nexport default ImportQuestionModal;\n","import React, { useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { Col, Collapse, Dropdown, Menu, Row, Spin, Tag, Upload } from 'antd';\nimport styled from 'styled-components';\nimport {\n CaretDownFilled,\n CaretRightOutlined,\n DeleteTwoTone,\n EditTwoTone,\n ImportOutlined,\n SearchOutlined,\n SettingOutlined,\n} from '@ant-design/icons';\n\nimport Title from 'components/Title';\nimport { StyledBotDetailsWrapper } from 'styles/GenericStyledComponents';\nimport useQuestions from './hooks';\nimport { cssVariables } from 'styles/root';\nimport TextArea from 'components/TextArea';\nimport { FlexRowSpaceBetweenWrapper } from 'components/Modals/AnswerEditor/Variant/Variant.styles';\nimport Modal from 'components/Modals/GenericModal';\nimport { StyledAntUploadContent } from 'components/Modals/ImportAnswerModal/ImportAnswerModal.style';\nimport Button from 'components/Button';\nimport Input from 'components/Input';\nimport ExportModal from 'components/Modals/ExportModal';\nimport SettingsDropdownMenu from './SettingsDropdownMenu/SettingsDropdownMenu';\nimport { StyledDropdownButtonMenu } from '../BotDetails.styles';\nimport QuestionImportExportMenu from './QuestionImportExportMenu/QuestionImportExportMenu';\nimport ImportQuestionModal from 'components/Modals/ImportQuestionModal';\n\nconst { Panel } = Collapse;\nconst { Dragger } = Upload;\n\nconst StyledCollapse = styled(Collapse)`\n .ant-collapse-arrow {\n height: 80% !important;\n padding: 0 !important;\n display: flex !important;\n align-items: center !important;\n }\n .ant-collapse-expand-icon {\n align-self: center;\n }\n\n .ant-collapse-header .ant-collapse-header-text {\n align-self: center;\n }\n\n .ant-collapse-extra {\n width: 30%;\n }\n`;\n\nconst StyledSearch = styled(Input)`\n border: none;\n border-bottom: 1px solid ${cssVariables.gray1} !important;\n // width: 24vw;\n`;\n\nconst Questions = props => {\n const {\n allQuestionsWithAnswer,\n filteredQuestions,\n loading,\n newQuestion,\n searchKey,\n showQuestionImporter,\n onSearchQuestion,\n onChangeQuestion,\n handleDeleteQuestion,\n handleEditQuestion,\n handleAddNewQuestion,\n handleUploadFile,\n setShowQuestionImporter,\n openExportModal,\n handleOpenExportModal,\n handleCloseExportModal,\n showImportModal,\n fileType,\n handleCloseImportModal,\n handleOpenImportModal,\n isMobileView,\n } = useQuestions();\n const ErrorPage = () => (\n \n
Cannot load the page. Please refresh your browser. \n \n );\n\n const questionsWithAnswer = useMemo(() => {\n if (filteredQuestions?.length && searchKey?.length) {\n return filteredQuestions;\n } else if (!searchKey) {\n return allQuestionsWithAnswer;\n }\n return [];\n }, [allQuestionsWithAnswer, filteredQuestions]);\n\n return (\n \n \n \n \n \n \n \n \n }\n isMainButtonDisabled={!newQuestion}\n onClick={evt => {\n evt.preventDefault();\n handleAddNewQuestion();\n }}\n overlay={\n \n }\n title={!newQuestion ? 'Question is required' : 'Save Question'}\n isQuestionPage={true}\n >\n {'Add Question'}\n \n \n
\n \n \n {'Search: '} \n \n \n \n }\n bordered={false}\n onChange={onSearchQuestion}\n value={searchKey}\n placeholder=\"Type question to search...\"\n />\n \n \n \n }\n menudisabled={!questionsWithAnswer?.length}\n trigger={['click']}\n >\n }\n variant=\"link\"\n ghost\n />\n \n \n
\n\n \n {questionsWithAnswer.length ? (\n questionsWithAnswer?.map((item, idx) => {\n return (\n (\n \n )}\n >\n \n \n {item.question?.version ? (\n \n {item.question?.version}\n \n ) : null}\n \n\n \n handleEditQuestion(item.question, item.answer)\n }\n >\n }\n full\n />\n \n \n handleDeleteQuestion(item.question?.jid)\n }\n >\n \n }\n full\n />\n \n \n }\n >\n \n {'Answer'}\n {': '}\n {item.answer?.text}\n
\n \n \n );\n })\n ) : (\n \n
Empty \n \n )}\n \n setShowQuestionImporter(false)}\n onCancel={() => setShowQuestionImporter(false)}\n >\n \n \n \n Drag files here or select file\n \n \n \n \n\n {openExportModal && (\n \n )}\n\n {showImportModal ? (\n \n ) : null}\n \n );\n};\n\nQuestions.propTypes = {};\n\nexport default Questions;\n","import { useState, useContext } from 'react';\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport {\n UPDATE_ANSWER_EDITOR_LINKED_QUESTIONS,\n UPDATE_SIMILAR_QUESTIONS_AFTER_HARD_LINK,\n} from 'store/action';\nimport { message } from 'antd';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { similarQuestionsModalSelector } from 'selectors/bot/ui';\nimport useSelector from 'store/useSelector';\n\nconst useQuestion = () => {\n const [state, dispatch] = useContext(Context);\n const similarQuestionsModal = useSelector(similarQuestionsModalSelector);\n const {\n sentinel,\n token,\n bot: { jid },\n } = state;\n const [viewAnswer, setViewAnswer] = useState(false);\n const [editing, setEditing] = useState(false);\n const [newQuestion, setNewQuestion] = useState(false);\n\n const handleDeleteHardLink = async questionId => {\n try {\n await apiService.deleteQuestionHardLink(questionId, sentinel, token);\n dispatch({\n type: UPDATE_SIMILAR_QUESTIONS_AFTER_HARD_LINK,\n payload: {\n questionId,\n },\n });\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleDeleteQuestionThenHardLinkAnswer = async (question, answerId) => {\n try {\n // question object with `jid` => the question item\n // from the list of similar questions\n\n // question from `itemToChange` => the current question\n // the user ask from the chat\n const { jid: questionId } = question;\n const { itemToChange } = similarQuestionsModal;\n await apiService.deleteQuestionThenHardLinkAnswer(\n // questionId to delete the question node\n questionId,\n // new question (text/string) to be hard linked to the answer\n // connected to the `questionId`\n itemToChange.question,\n answerId,\n jid,\n sentinel,\n token\n );\n\n const res = await apiService.getSimilarQuestions(\n itemToChange.question,\n sentinel,\n jid,\n token\n );\n dispatch({\n type: UPDATE_SIMILAR_QUESTIONS_AFTER_HARD_LINK,\n payload: {\n data: res.data.report[0],\n itemToChange: {\n question: itemToChange.question,\n answer: itemToChange.answer,\n idx: itemToChange?.idx || 0,\n },\n },\n });\n return message.success(\n 'Successfully hard link the current question to new answer'\n );\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleSaveNewQuestion = async (questionId, answerId) => {\n try {\n const res = await apiService.editQuestion(\n newQuestion,\n answerId,\n questionId,\n sentinel,\n token\n );\n dispatch({\n type: UPDATE_ANSWER_EDITOR_LINKED_QUESTIONS,\n payload: res.data.report,\n });\n } catch (error) {}\n };\n\n return {\n editing,\n handleDeleteHardLink,\n handleDeleteQuestionThenHardLinkAnswer,\n handleSaveNewQuestion,\n setEditing,\n setNewQuestion,\n setViewAnswer,\n viewAnswer,\n };\n};\n\nexport default useQuestion;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport {\n ApiFilled,\n ApiTwoTone,\n DeleteFilled,\n DeleteTwoTone,\n EditTwoTone,\n EyeTwoTone,\n} from '@ant-design/icons';\nimport { Dropdown, Menu } from 'antd';\n\nimport { cssVariables } from 'styles/root';\nimport AnchorButton from 'components/Button/AnchorButton';\nimport {\n StyledFlexColumn,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\nimport useQuestion from './hooks';\nimport { StyledMore } from 'components/AnswerItem/Answer.styles';\nimport { StyledAnswerScore } from 'components/AnswerWithScore/AnswerWithScore.styles';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\nimport { FlexRowSpaceBetweenWrapper } from 'components/Modals/AnswerEditor/Variant/Variant.styles';\nimport Button from 'components/Button';\nimport Input from 'components/Input';\nimport { AnswerRenderer } from 'components/AnswerRenderer';\n\nconst QuestionFlexColumn = styled(StyledFlexColumn)`\n box-shadow: ${cssVariables.itemListShadow};\n padding: 10px;\n\n .ant-input {\n width: 100%;\n }\n`;\n\nconst StyledQuestionMoreMenu = styled(StyledMore)`\n width: 36px;\n background-color: ${cssVariables.gray2};\n border-radius: 50%;\n`;\n\nconst StyledLeftActionButtons = styled.div`\n display: flex;\n align-items: center;\n flex-direction: row;\n\n .anticon {\n font-size: 14px;\n }\n`;\n\nconst Question = ({ item, showDisplayAnswer, deleteOnly, noScore }) => {\n const {\n editing,\n handleDeleteHardLink,\n handleDeleteQuestionThenHardLinkAnswer,\n handleSaveNewQuestion,\n setEditing,\n setNewQuestion,\n setViewAnswer,\n viewAnswer,\n } = useQuestion();\n\n // component prop\n const { question, answer, score } = item;\n\n const handleClickMenuItem = item => {\n switch (item.key) {\n case 'show-answer': {\n return setViewAnswer(!viewAnswer);\n }\n case 'delete-question-and-link-answer':\n return handleDeleteQuestionThenHardLinkAnswer(question, answer?.jid);\n case 'remove-hard-link':\n return handleDeleteHardLink(question?.jid);\n default:\n return false;\n }\n };\n\n const QUESTION_MENU_ITEMS = [\n {\n key: 'show-answer',\n label: viewAnswer ? 'Hide Answer' : 'Show Answer',\n icon: ,\n },\n {\n key: 'delete-question-and-link-answer',\n label: 'Remove Question then Hard Link Answer',\n icon: ,\n },\n {\n key: 'remove-hard-link',\n label: 'Remove Question Link',\n icon: ,\n },\n ];\n\n const moreMenu = (\n \n {QUESTION_MENU_ITEMS.map(item => (\n \n {item.label}\n \n ))}\n \n );\n\n return (\n \n \n \n
\n {deleteOnly ? (\n \n ) : (\n \n {score && !noScore ? score?.toFixed(3) : }\n \n )}\n \n {!editing ? (\n
{question?.context?.text} \n ) : (\n
setNewQuestion(evt.target.value)}\n />\n )}\n
\n \n {deleteOnly ? (\n \n {editing ? (\n \n handleSaveNewQuestion(question?.jid, answer?.jid)\n }\n />\n ) : (\n setEditing(true)} />\n )}\n handleDeleteHardLink(question?.jid)}\n />\n \n ) : (\n \n \n \n )}\n \n \n {viewAnswer ? (\n \n {\n \n \n \n {' Hard Linked Answer: '}\n \n \n handleDeleteQuestionThenHardLinkAnswer(question, answer?.jid)\n }\n />\n \n }\n \n \n ) : null}\n \n );\n};\n\nQuestion.propTypes = {\n item: PropTypes.object,\n showDisplayAnswer: PropTypes.bool,\n deleteOnly: PropTypes.bool,\n noScore: PropTypes.bool,\n};\n\nexport default Question;\n","import { message } from 'antd';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { useContext } from 'react';\nimport { useState } from 'react';\n\nimport { similarQuestionsModalSelector } from 'selectors/bot/ui';\nimport { getTokenSelector } from 'selectors/user';\nimport { apiService } from 'services/api.service';\nimport {\n CLOSE_SIMILAR_QUESTIONS_MODAL,\n SHOW_QUESTION_EDITOR,\n SHOW_RESPONSE_PICKER_MODAL_WITH_ANSWERSCORE_PAYLOAD,\n} from 'store/action';\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\n\nconst useSimilarQuestionsModal = () => {\n const [state, dispatch] = useContext(Context);\n const {\n sentinel,\n bot: { jid, name },\n } = state;\n const token = useSelector(getTokenSelector);\n const similarQuestionsModal = useSelector(similarQuestionsModalSelector);\n\n const [loading, setLoading] = useState(false);\n\n const handleCloseSimilarQuestionsModal = () => {\n dispatch({\n type: CLOSE_SIMILAR_QUESTIONS_MODAL,\n });\n };\n\n const openResponseModalPicker = async (text, answer, idx) => {\n setLoading(true);\n if (\n text === 'string' &&\n !text.length &&\n !answer &&\n !Object.keys(similarQuestionsModal?.itemToChange)?.length\n ) {\n return false;\n }\n const question =\n typeof text === 'string' && text.length\n ? text\n : similarQuestionsModal.itemToChange?.question;\n const responsePickerSelectedAnswer =\n typeof answer === 'object' && Object.keys(answer).length\n ? answer\n : // answer came from similarAnswersModal\n similarQuestionsModal.itemToChange?.answer;\n\n const title = 'Hard Link Question to Answer';\n const res = await apiService.getQuestionMatchesWithAnswer(\n question,\n null,\n sentinel,\n jid,\n token\n );\n\n if (!res.data?.report[0]) {\n loading(false);\n return message.error(DEFAULT_ERROR_MESSAGE);\n }\n\n handleCloseSimilarQuestionsModal();\n await dispatch({\n type: SHOW_RESPONSE_PICKER_MODAL_WITH_ANSWERSCORE_PAYLOAD,\n payload: {\n answers: res.data.report[0],\n botName: name,\n withQuestion: true,\n editableQuestion: true,\n title,\n action: similarQuestionsModal.action,\n isHardLinked: responsePickerSelectedAnswer?.isHardLinked,\n answerId: responsePickerSelectedAnswer?.jid,\n itemToChange: {\n question: { text: question },\n answer: responsePickerSelectedAnswer,\n idx: idx || similarQuestionsModal?.itemToChange?.idx || 0,\n },\n },\n });\n setLoading(false);\n };\n\n const openQuestionEditor = async (text, answer, idx) => {\n if (\n text === 'string' &&\n !text.length &&\n !answer &&\n !Object.keys(similarQuestionsModal?.itemToChange)?.length\n ) {\n return false;\n }\n const question =\n typeof text === 'string' && text.length\n ? text\n : similarQuestionsModal.itemToChange?.question;\n const responsePickerSelectedAnswer =\n typeof answer === 'object' && Object.keys(answer).length\n ? answer\n : // answer came from similarAnswersModal\n similarQuestionsModal.itemToChange?.answer;\n const res = await apiService.getSimilarQuestions(\n question,\n sentinel,\n jid,\n token\n );\n\n if (!res.data?.report[0]) {\n setLoading(false);\n return message.error(DEFAULT_ERROR_MESSAGE);\n }\n\n await dispatch({\n type: CLOSE_SIMILAR_QUESTIONS_MODAL,\n });\n await dispatch({\n type: SHOW_QUESTION_EDITOR,\n payload: {\n answersWithScore: res.data.report[0],\n action: similarQuestionsModal?.action || 'add',\n isHardLinked: responsePickerSelectedAnswer?.isHardLinked,\n answerId: responsePickerSelectedAnswer?.jid,\n text: question,\n jid: responsePickerSelectedAnswer?.isHardLinked\n ? responsePickerSelectedAnswer.questionId\n : null,\n },\n });\n };\n\n return {\n handleCloseSimilarQuestionsModal,\n loading,\n openResponseModalPicker,\n openQuestionEditor,\n similarQuestionsModal,\n };\n};\n\nexport default useSimilarQuestionsModal;\n","import React from 'react';\nimport Modal from '../GenericModal';\nimport Question from 'components/Question';\nimport useSimilarQuestionsModal from './hooks';\n\nconst SimilarQuestionsModal = () => {\n const {\n handleCloseSimilarQuestionsModal,\n loading,\n openResponseModalPicker,\n openQuestionEditor,\n similarQuestionsModal,\n } = useSimilarQuestionsModal();\n\n if (!similarQuestionsModal?.isOpen) {\n return false;\n }\n return (\n \n {similarQuestionsModal.isOpen && similarQuestionsModal.data?.length ? (\n <>\n {'Users Question:'} \n \n\n {similarQuestionsModal.itemToChange.question} \n
\n {'Similar Questions:'} \n {similarQuestionsModal.data.map((item, idx) => {\n return ;\n })}\n >\n ) : (\n 'No Similar questions found!'\n )}\n \n );\n};\n\nSimilarQuestionsModal.propTypes = {};\n\nexport default SimilarQuestionsModal;\n","import Button from 'components/Button';\nimport styled from 'styled-components';\nimport { cssVariables } from 'styles/root';\nimport Alert from 'components/Alert';\n\nexport const CategoryFieldWrapper = styled.div`\n display: flex;\n > * + * {\n margin-left: 5px;\n }\n`;\n\nexport const StyledAddVariantButton = styled(Button)`\n width: fit-content;\n font-size: 13px;\n height: auto;\n margin: 10px 0;\n\n :hover,\n :focus,\n :active {\n background-color: ${cssVariables.gray1};\n color: ${cssVariables.primaryColor};\n }\n\n .anticon {\n margin-left: 5px;\n font-size: 18px;\n font-weight: ${cssVariables.font.bold};\n }\n`;\n\nexport const StyledAnswerEditor = styled.div`\n display: flex;\n flex-direction: column;\n min-width: 100%;\n\n @media (max-width: 600px) {\n min-width: 100%;\n }\n`;\n\nexport const StyledTitleWithTip = styled.div`\n display: flex;\n align-items: center;\n\n & h4 {\n margin: 0;\n }\n`;\n\nexport const StyledTopText = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: -5px;\n\n & h4 {\n font-size: 14px;\n font-weight: ${cssVariables.font.bold};\n }\n\n & span {\n font-size: 12px;\n }\n`;\n\nexport const StyledDisplayAnswerEditor = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n\n & span:first-child {\n font-weight: ${cssVariables.font.bold};\n }\n\n & .ant-radio-button-wrapper {\n color: ${cssVariables.primaryColor};\n }\n\n & .ant-radio-button-wrapper-checked {\n border-color: ${cssVariables.primaryTransparent} !important;\n background-color: ${cssVariables.primaryTransparent} !important;\n\n &::before {\n background-color: transparent;\n }\n }\n`;\n\nexport const StyledSectionContent = styled.div`\n display: flex;\n flex-direction: column;\n row-gap: 5px;\n margin-bottom: 5px;\n margin-top: 5px;\n width: 100%;\n & h4 {\n font-weight: ${cssVariables.font.bold};\n }\n`;\n\nexport const StyledToggleLink = styled.div`\n & button {\n text-transform: capitalize;\n font-size: 12px;\n\n & .MuiButton-startIcon span {\n font-size: 14px;\n }\n }\n`;\n\nexport const StyledEditorDropdownWrapper = styled.div`\n display: flex;\n align-items: center;\n & h4 {\n margin: 0;\n }\n`;\n\nexport const StyledErrorMsg = styled(Alert)`\n margin: 0 auto;\n`;\n\nexport const StyledQuickBtnToggleOption = styled.div`\n width: 100%;\n`;\n\nexport const StyledQuickBtnToggleWrapper = styled.div`\n display: flex;\n width: 100%;\n`;\n\nexport const StyledQuestionsWrapper = styled.div`\n margin: 5px;\n ul {\n padding: 0px;\n }\n`;\n","import styled from 'styled-components';\n\nexport const StyledRoot = styled.div`\n width: 100%;\n\n & .ant-select {\n width: 100%;\n }\n\n & .ant-select-selector {\n border-radius: 5px;\n }\n`;\n","import React from 'react';\nimport PropTypes from 'prop-types';\n\nimport { Select } from 'antd';\nimport { StyledRoot } from './CategorySelect.styles';\nimport { stripUUID } from 'utils';\n\nconst LABEL_COLOR = '#fff';\n\nconst CategorySelect = ({\n label,\n categories,\n isCategoryModal,\n selectedCategory,\n handleChange,\n placeholder,\n}) => {\n const currentCategory = categories.find(\n c => stripUUID(c.jid) === stripUUID(selectedCategory)\n );\n\n return (\n \n {!!label && {label} }\n \n option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0\n }\n >\n \n {'Select Category'}\n \n {categories.map((category, idx) => {\n return (\n \n {category.name}\n \n );\n })}\n \n \n );\n};\n\nCategorySelect.propTypes = {\n categories: PropTypes.array.isRequired,\n isCategoryModal: PropTypes.bool,\n handleChange: PropTypes.func.isRequired,\n selectedCategory: PropTypes.string,\n label: PropTypes.string,\n placeholder: PropTypes.string,\n};\n\nexport default CategorySelect;\n","import { useState, useEffect, useContext } from 'react';\nimport { message } from 'antd';\n\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport { strippedString } from 'utils/stringManipulation';\nimport { isAnObject } from 'utils/dataTypes';\nimport {\n DEFAULT_EDITOR,\n MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION,\n ZSB_CHAT_BREAKER_ENCONDING,\n} from 'constants/answerbank/defaults';\nimport {\n ADD_ANSWER,\n CLOSE_ANSWER_EDITOR,\n SET_ANSWER_EDITOR_CARD_INSIGHTS,\n SET_ANSWER_EDITOR_GRAPH_INSIGHTS,\n SET_ANSWER_EDITOR_LINKED_QUESTIONS,\n SET_ANSWER_EDITOR_TABLE_INSIGHTS,\n UPDATE_ANSWER,\n UPDATE_ANSWER_EDITOR_ANSWER,\n} from 'store/action';\nimport {\n DEFAULT_ANSWER_VERSION,\n CREATE_ANSWER_MANUAL_SOURCE,\n CREATE_ANSWER_MANUAL_SOURCE_TYPE,\n} from 'constants/answerbank/defaults';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport {\n answerEditorAnswerSelector,\n answerEditorQuestionsSelector,\n answerEditorSimilarAnswerSelector,\n allAnswersSelector,\n isAnswerEditorOpenSelector,\n isCreateModeAnswerEditorSelector,\n isMaxAnswersSelector,\n answerEditorInsightsSelector,\n} from 'selectors/bot/answers';\nimport { allCategoriesSelector } from 'selectors/bot/categories';\nimport {\n getGraphTimeInterval,\n isElasticAggregationsEmpty,\n} from 'utils/analytics';\nimport useSelector from 'store/useSelector';\nimport { convertToEndOfDay, convertToStartOfDay } from 'utils/dates';\n\nconst MAX_QUICK_REPLY_ROWS = 3;\nconst MIN_QUICK_REPLY_ROWS = 0;\nconst initialQuickReplies = {\n quickReply: false,\n quickReplyOptions: [\n {\n label: '',\n question: '',\n editableQuestion: false,\n answer: '',\n key: 0,\n },\n ],\n};\n\nconst useAnswerEditor = () => {\n const [state, dispatch] = useContext(Context);\n const allAnswers = useSelector(allAnswersSelector);\n const allCategories = useSelector(allCategoriesSelector);\n const answerEditorAnswer = useSelector(answerEditorAnswerSelector);\n const answerEditorQuestions = useSelector(answerEditorQuestionsSelector);\n const answerEditorInsights = useSelector(answerEditorInsightsSelector);\n const isOpen = useSelector(isAnswerEditorOpenSelector);\n const isCreateMode = useSelector(isCreateModeAnswerEditorSelector);\n const similarAnswers = useSelector(answerEditorSimilarAnswerSelector);\n const limitReached = useSelector(isMaxAnswersSelector);\n const {\n sentinel,\n token,\n bot: { jid },\n } = state;\n\n const [answer, setAnswer] = useState(answerEditorAnswer);\n const [confirmLoading, setConfirmLoading] = useState(false);\n const [tabView, setTabView] = useState(null);\n const [editor, setEditor] = useState(answerEditorAnswer?.editor);\n const [error, setError] = useState(null);\n const [editedHTML, setEditedHTML] = useState(answerEditorAnswer?.html);\n const [editedRichTextAnswer, setEditedRichTextAnswer] = useState(\n answerEditorAnswer?.rich_text\n );\n const [editedStringAnswer, setEditedStringAnswer] = useState(\n answerEditorAnswer?.text\n );\n const [selectedCategory, setSelectedCategory] = useState(\n answerEditorAnswer?.categoryId || null\n );\n const [showAdvanceSettings, setShowAdvanceSettings] = useState(true);\n const [dragId, setDragId] = useState(null);\n const [isTextCategory, setIsTextCategory] = useState(false);\n const [textCategoryValue, setTextCategoryValue] = useState(null);\n const [categoryColor, setCategoryColor] = useState(null);\n const [showColorPicker, setShowColorPicker] = useState(false);\n const [quickReplies, setQuickReplies] = useState(initialQuickReplies);\n const [isFullScreen, setFullScreen] = useState(false);\n const [answerVariants, setAnswerVariants] = useState([]);\n const [successMessage, setSuccssMessage] = useState(null);\n const [shouldUseSkeletonComponent, setShouldUseSkeletonComponent] =\n useState(false);\n const DEFAULT_ANSWER_VARIANT = {\n forms: [{ key: 1, select1: '', select2: '', select3: [''] }],\n displayAnswer: {\n html: '',\n rce: '',\n },\n editor: DEFAULT_EDITOR,\n key: answerVariants ? answerVariants.length + 1 : 1,\n };\n const startDateInMS = new Date().setDate(new Date().getDate() - 7);\n const startDate = new Date(startDateInMS);\n const todayInMS = new Date();\n const todayISO = new Date(todayInMS);\n const [dateFilter, setDateFilter] = useState({\n startDate: startDate,\n endDate: todayISO,\n });\n const [sessionFilteredInfo, setSessionFilteredInfo] = useState(null);\n const [parsedAnswer, setParsedAnswer] = useState([]);\n\n useEffect(() => {\n if (isOpen && isAnObject(answerEditorAnswer)) {\n if (isCreateMode && similarAnswers) {\n setAnswer({ ...answerEditorAnswer, version: 'draft' });\n }\n\n if (answerEditorAnswer?.quickReply) {\n setQuickReplies({\n ...quickReplies,\n quickReply: answerEditorAnswer.quickReply,\n quickReplyOptions: answerEditorAnswer.quickReplyOptions,\n });\n } else if (answerEditorAnswer.requestAgent) {\n setRequestAgent(true);\n }\n }\n\n return () => {\n handleClearAnswerEditorLocalState();\n setAnswer({});\n };\n }, [answerEditorAnswer]);\n\n const [requestAgent, setRequestAgent] = useState(\n answer?.requestAgent || false\n );\n const isRemoveRowDisabled =\n quickReplies?.quickReplyOptions?.length <= MIN_QUICK_REPLY_ROWS;\n\n const handleChangeRequestAgentCheckbox = () => {\n setRequestAgent(!requestAgent);\n setQuickReplies({\n ...quickReplies,\n quickReply: false,\n });\n };\n\n const scrollIntoWarningView = () => {\n setTimeout(() => {\n const target = document.getElementsByClassName('ant-alert');\n if (target && target[0]) {\n target[0].scrollIntoViewIfNeeded();\n }\n }, 500);\n };\n\n const handleOnUpdateAnswer = async istemp => {\n setConfirmLoading(true);\n\n // get similar answers instead if:\n // answer has not been edited\n // no similar answers has been set/found on state\n // answer version is final\n let hasNewSimilarAnswers = false;\n if (\n answerEditorAnswer.text !== editedStringAnswer &&\n answer.version === 'final'\n ) {\n hasNewSimilarAnswers = await getSimilarAnswers(answer.version);\n }\n if (hasNewSimilarAnswers) {\n return false;\n }\n\n const formattedAnswer =\n editor === DEFAULT_EDITOR ? editedRichTextAnswer : editedHTML;\n\n const quickButtons = {\n ...quickReplies,\n requestAgent,\n };\n\n try {\n const hasEmptyQuickReply =\n quickReplies?.quickReply &&\n (quickButtons?.quickReplyOptions || []).some(\n item =>\n // trim non-alphanemuric values\n !strippedString(item.label) ||\n !strippedString(item.label).length ||\n !strippedString(item.question) ||\n !strippedString(item.question).length\n );\n\n if (hasEmptyQuickReply) {\n throw new Error(\n 'Empty quick reply input field found.\\n Please fill out all quick reply fields or delete the entire row'\n );\n }\n\n // not sure if this is still needed\n if (istemp && !answerEditorAnswer.clone) {\n await onUpdateAnswer(\n null,\n editedStringAnswer,\n formattedAnswer,\n selectedCategory,\n answerEditorAnswer.idx,\n editor,\n quickButtons,\n answer?.version || DEFAULT_ANSWER_VERSION\n );\n return handleClose();\n } else if (answerEditorAnswer.clone) {\n await handleCloneAnswer(\n editedStringAnswer,\n formattedAnswer,\n editor,\n selectedCategory,\n answer?.version || DEFAULT_ANSWER_VERSION\n );\n } else {\n await onUpdateAnswer(\n answerEditorAnswer.id,\n editedStringAnswer,\n formattedAnswer,\n selectedCategory,\n answerEditorAnswer.idx,\n editor,\n quickButtons,\n answer?.version || DEFAULT_ANSWER_VERSION\n );\n }\n setConfirmLoading(false);\n handleClose();\n } catch (error) {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n setConfirmLoading(false);\n };\n\n const onUpdateAnswer = async (\n id,\n newAnswer,\n formattedAnswer,\n categoryId,\n chatIndex = null,\n editor,\n quickButtons,\n version\n ) => {\n setConfirmLoading(true);\n const isAnswerExists = getStringAnswerDuplicate(newAnswer, id);\n try {\n if (isAnswerExists) {\n throw new TypeError('Answer already exists.');\n }\n const res = await apiService.changeAnswer(\n sentinel,\n id,\n newAnswer,\n formattedAnswer,\n categoryId,\n token,\n editor,\n quickButtons,\n version\n );\n const updatedAnswer = res.data.report[0];\n\n dispatch({\n type: UPDATE_ANSWER,\n payload: { updatedAnswer, msgIndex: chatIndex },\n });\n\n setConfirmLoading(false);\n } catch (err) {\n setConfirmLoading(false);\n throw err.message;\n }\n };\n\n const handleOnAddAnswer = async skipSimilarCheck => {\n try {\n setConfirmLoading(true);\n const isAnswerExists = getStringAnswerDuplicate(answer?.text);\n try {\n if (limitReached) {\n throw new TypeError(\n 'Maximum number of answers reached. Please upgrade your plan.'\n );\n }\n if (isAnswerExists) {\n throw new TypeError('Answer already exists.');\n }\n\n const answerdetails = {\n displayAnswer: answer?.text || answerEditorAnswer.text,\n text: answer?.text || answerEditorAnswer.text,\n };\n\n const res = await apiService.createAnswer(\n sentinel,\n jid,\n {\n source: CREATE_ANSWER_MANUAL_SOURCE,\n source_type: CREATE_ANSWER_MANUAL_SOURCE_TYPE,\n },\n answerdetails,\n token,\n skipSimilarCheck,\n answerEditorAnswer?.version\n );\n const answerData = res.data.report[0];\n if (Array.isArray(answerData)) {\n dispatch({\n type: UPDATE_ANSWER_EDITOR_ANSWER,\n payload: {\n // We're including the component state of the `answer` to the payload\n // since updating the similarAnswer's global state will cause rerender\n // and the component state will be lost\n text: editedStringAnswer,\n version: answer?.version,\n // ditched using component state to store similarAnswers data\n similarAnswers: answerData,\n },\n });\n } else {\n dispatch({\n type: ADD_ANSWER,\n payload: answerData,\n });\n message.success('Successfully added an answer.');\n }\n } catch (err) {\n message.error(\n err.message || 'Unexpected error encountered while saving.'\n );\n }\n setConfirmLoading(false);\n handleClose();\n } catch (error) {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n setConfirmLoading(false);\n };\n\n const handleSelectCategory = categoryId => {\n setSelectedCategory(categoryId);\n };\n\n const getStringAnswerDuplicate = (input, id) => {\n const strippedInput = strippedString(input);\n return allAnswers.find(\n answer =>\n strippedString(answer.text) === strippedInput && answer.jid !== id\n );\n };\n\n const handleCloneAnswer = async (\n newAnswer,\n formattedAnswer,\n editor = DEFAULT_EDITOR,\n categoryId,\n ansVersion\n ) => {\n setConfirmLoading(true);\n const isAnswerExists = getStringAnswerDuplicate(newAnswer);\n try {\n if (isAnswerExists) {\n setConfirmLoading(false);\n throw new TypeError('Answer already exists.');\n }\n const answerdetails = {\n displayAnswer: formattedAnswer,\n text: newAnswer,\n };\n const res = await apiService.createAnswer(\n sentinel,\n jid,\n {\n source: CREATE_ANSWER_MANUAL_SOURCE,\n source_type: CREATE_ANSWER_MANUAL_SOURCE_TYPE,\n },\n answerdetails,\n token,\n false,\n ansVersion,\n editor,\n categoryId\n );\n const answerData = res.data.report[0];\n dispatch({\n type: ADD_ANSWER,\n payload: answerData,\n });\n } catch (err) {\n setConfirmLoading(false);\n throw err.message;\n }\n setConfirmLoading(false);\n };\n\n const handleQuickReplyChange = (e, type, key) => {\n const updatedArr = [];\n const { quickReplyOptions } = quickReplies;\n for (const item of quickReplyOptions) {\n if (item.key === key) {\n if (type === 'edit') {\n updatedArr.push({\n ...item,\n editableQuestion: !item.editableQuestion,\n });\n } else if (type === 'unlink') {\n updatedArr.push({\n ...item,\n answer: null,\n editableQuestion: false,\n });\n } else if (type === 'label') {\n updatedArr.push({\n ...item,\n label: e.target.value,\n });\n } else if (type === 'answer') {\n updatedArr.push({\n ...item,\n answer: e,\n });\n } else if (type === 'question') {\n updatedArr.push({\n ...item,\n question: e.target.value,\n });\n } else if (!item.editableQuestion) {\n updatedArr.push({\n ...item,\n label: e.target.value,\n question: e.target.value,\n answer: '',\n });\n } else {\n updatedArr.push({ ...item, question: e.target.value });\n }\n } else {\n updatedArr.push(item);\n }\n }\n\n setQuickReplies({\n ...quickReplies,\n quickReplyOptions: [...updatedArr],\n });\n };\n\n const handleDrag = evt => {\n setDragId(Number(evt.currentTarget.id));\n };\n\n const handleDrop = evt => {\n const { quickReplyOptions } = quickReplies;\n const dragRow = (quickReplyOptions || []).find(item => item.key === dragId);\n const dropRow = (quickReplyOptions || []).find(\n item => item.key === Number(evt.currentTarget.id)\n );\n\n const dragBoxOrder = dragRow.key;\n const dropBoxOrder = dropRow.key;\n\n const newRowOrder = (quickReplyOptions || []).map(item => {\n // - 1\n if (item.key === dragId) {\n item.key = dropBoxOrder;\n }\n if (item.key === Number(evt.currentTarget.id)) {\n item.key = dragBoxOrder;\n }\n return item;\n });\n\n setQuickReplies({\n ...quickReplies,\n quickReplyOptions: newRowOrder,\n });\n };\n\n const isAddRowDisabled = () => {\n const { quickReplyOptions } = quickReplies;\n\n if (quickReplyOptions) {\n return (quickReplyOptions || []).find(\n item => !item.label.length || !item.question.length\n ) || quickReplyOptions.length >= MAX_QUICK_REPLY_ROWS\n ? true\n : false;\n }\n return false;\n };\n\n const addRow = () => {\n const isDisabled = isAddRowDisabled();\n if (isDisabled) {\n return false;\n } else {\n setQuickReplies({\n ...quickReplies,\n quickReplyOptions: [\n ...quickReplies.quickReplyOptions,\n {\n label: '',\n question: '',\n key: quickReplies.quickReplyOptions?.length,\n editableQuestion: false,\n },\n ],\n });\n }\n };\n\n const deleteRow = idx => {\n const { quickReplyOptions } = quickReplies;\n const filteredQuickReplies = (quickReplyOptions || []).filter(\n (item, key) => key !== idx\n );\n\n if (isRemoveRowDisabled) {\n return false;\n } else if (filteredQuickReplies.length === 0) {\n setQuickReplies({\n quickReply: false,\n quickReplyOptions: [...filteredQuickReplies],\n });\n } else {\n setQuickReplies({\n ...quickReplies,\n quickReplyOptions: [...filteredQuickReplies],\n });\n }\n };\n\n const updateEditedStringAnswer = async evt => {\n setEditedStringAnswer(evt.target.value);\n setError(null);\n };\n\n const updateFormattedAnswer = async (val, plainText) => {\n setEditedHTML(val);\n setEditedRichTextAnswer(val);\n };\n\n const handleClearAnswerEditorLocalState = () => {\n setFullScreen(false);\n setAnswerVariants([]);\n setEditedRichTextAnswer(null);\n setEditedHTML(null);\n setQuickReplies(initialQuickReplies);\n setRequestAgent(false);\n setEditor(DEFAULT_EDITOR);\n setError(null);\n setConfirmLoading(false);\n setTabView(null);\n setAnswer({});\n };\n\n const handleClose = () => {\n if (isFullScreen) {\n return setFullScreen(false);\n }\n handleClearAnswerEditorLocalState();\n dispatch({\n type: CLOSE_ANSWER_EDITOR,\n });\n };\n\n const handleAddAnswerVariant = () => {\n setAnswerVariants([...answerVariants, DEFAULT_ANSWER_VARIANT]);\n };\n\n const handleUpdateVariantAnswer = (editedVariant, isOnlyForm) => {\n const updatedVariants = answerVariants\n .map(i => {\n if (editedVariant.key === i.key && isOnlyForm) {\n return null;\n } else if (editedVariant.key === i.key) {\n return editedVariant;\n }\n return i;\n })\n .filter(i => !!i);\n\n setAnswerVariants(updatedVariants);\n };\n\n const getSimilarAnswers = async version => {\n setShouldUseSkeletonComponent(true);\n let res;\n try {\n res = await apiService.getSimilarAnswer(\n sentinel,\n jid,\n editedStringAnswer || answerEditorAnswer.text,\n answerEditorAnswer.id || null,\n token\n );\n const quickButtons = {\n ...quickReplies,\n requestAgent,\n };\n const MAX_ANSWERS_SCORE = res.data.report[0].filter(\n item => item.score > MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION\n );\n\n dispatch({\n type: UPDATE_ANSWER_EDITOR_ANSWER,\n payload: {\n // We're including the component state of the `answer` to the payload\n // since updating the similarAnswer's global state will cause rerender\n // and the component state will be lost\n text: editedStringAnswer,\n html: editedHTML,\n rich_text: editedRichTextAnswer,\n categoryId: selectedCategory,\n idx: answerEditorAnswer.idx,\n editor,\n quickButtons,\n version:\n MAX_ANSWERS_SCORE.length === 0 && version ? version : 'draft',\n // ditched using component state to store similarAnswers data\n similarAnswers: res.data.report[0],\n },\n });\n setShouldUseSkeletonComponent(false);\n\n if (!res.data.report[0]?.length) {\n setSuccssMessage('No similar answer found.');\n return false;\n }\n return true;\n } catch (error) {}\n setShouldUseSkeletonComponent(false);\n };\n\n const handleChangeSwitch = async e => {\n try {\n await getSimilarAnswers(e ? 'final' : 'draft');\n } catch (error) {\n message.error(error.message || GET_DATA_ERROR);\n }\n };\n\n const handleChangeDateRange = (startDate, endDate) => {\n setDateFilter({\n startDate,\n endDate,\n });\n };\n\n const handleFetchInsightFromDateFilter = () => {\n fetchAggregationsData();\n };\n\n const sortFields = field => {\n switch (field) {\n case 'visitorID':\n return `metadata.${field}`;\n case 'date':\n return `datetime`;\n case 'validation':\n return `validation`;\n case 'channel':\n return `metadata.${field}.keyword`;\n case 'sessionID':\n return `metadata.${field}.keyword`;\n default:\n return `${field}.raw`;\n }\n };\n\n const handleTableChange = (pagination, filters, sorter) => {\n setSessionFilteredInfo(filters.sessionID);\n fetchTableData({\n page: 1,\n size: 1000,\n sort: {\n [sortFields(sorter.field)]: sorter.order === 'ascend' ? 'asc' : 'desc',\n },\n });\n };\n\n const fetchGraphData = async () => {\n setConfirmLoading(true);\n const startDate = convertToStartOfDay(dateFilter.startDate);\n const endDate = convertToEndOfDay(dateFilter.endDate);\n const dateInterval = getGraphTimeInterval(startDate, endDate);\n\n try {\n const ESRes = await apiService.getElasticByIntervalLogDate(\n sentinel,\n jid,\n startDate,\n endDate,\n dateInterval,\n [],\n null,\n token,\n { answerIds: [answerEditorAnswer.id] }\n );\n\n dispatch({\n type: SET_ANSWER_EDITOR_GRAPH_INSIGHTS,\n payload: {\n data: ESRes.data.report[0],\n startDate,\n endDate,\n },\n });\n } catch (error) {}\n setConfirmLoading(false);\n };\n\n const fetchTableData = async params => {\n const startDate = convertToStartOfDay(dateFilter.startDate);\n const endDate = convertToEndOfDay(dateFilter.endDate);\n try {\n const ESRes = await apiService.getElasticLogDateFilter(\n sentinel,\n jid,\n startDate,\n endDate,\n token,\n [],\n [],\n {\n ...params,\n }\n );\n\n dispatch({\n type: SET_ANSWER_EDITOR_TABLE_INSIGHTS,\n payload: {\n data: ESRes.data.report[0],\n startDate,\n endDate,\n },\n });\n } catch (error) {\n message.error(\n error.message !== DEFAULT_ERROR_MESSAGE ? error.message : GET_DATA_ERROR\n );\n }\n };\n\n const fetchAggregationsData = async (filterParams = {}) => {\n const params = {\n size: 1000,\n from: 0,\n page: 1,\n answerIds: [answerEditorAnswer.id],\n ...filterParams,\n };\n\n const startDate = convertToStartOfDay(dateFilter.startDate);\n const endDate = convertToEndOfDay(dateFilter.endDate);\n setShouldUseSkeletonComponent(true);\n\n try {\n const ESRes = await apiService.getElasticLogDateFilter(\n sentinel,\n jid,\n startDate,\n endDate,\n token,\n [],\n [],\n {\n ...params,\n size: '0',\n for_aggs: true,\n }\n );\n\n dispatch({\n type: SET_ANSWER_EDITOR_CARD_INSIGHTS,\n payload: {\n data: ESRes.data.report[0],\n startDate,\n endDate,\n },\n });\n setShouldUseSkeletonComponent(false);\n const isAggregationsEmpty = isElasticAggregationsEmpty(\n ESRes?.data?.report[0]\n );\n if (!isAggregationsEmpty) {\n fetchGraphData();\n fetchTableData(params);\n }\n } catch (error) {\n message.error(\n error.message !== DEFAULT_ERROR_MESSAGE ? error.message : GET_DATA_ERROR\n );\n }\n };\n\n const handleChangeTabView = async tabName => {\n switch (tabName) {\n case 'question': {\n setShouldUseSkeletonComponent(true);\n setTabView('question');\n if (!answerEditorQuestions?.length) {\n try {\n const res = await apiService.getHardLinkQuestionsFromAnswer(\n answerEditorAnswer.id,\n sentinel,\n token\n );\n if (res.data.report[0]) {\n dispatch({\n type: SET_ANSWER_EDITOR_LINKED_QUESTIONS,\n payload: res.data.report[0],\n });\n }\n } catch (error) {\n message.error(GET_DATA_ERROR);\n }\n }\n setShouldUseSkeletonComponent(false);\n break;\n }\n case 'answer': {\n setTabView('answer');\n break;\n }\n case 'insights': {\n setTabView('insights');\n await fetchAggregationsData();\n break;\n }\n default:\n break;\n }\n };\n\n return {\n answer,\n categoryColor,\n confirmLoading,\n editedHTML,\n editedRichTextAnswer,\n editedStringAnswer,\n editor,\n error,\n jid,\n quickReplies,\n requestAgent,\n selectedCategory,\n showAdvanceSettings,\n addRow,\n deleteRow,\n handleAddAnswerVariant,\n handleClose,\n handleDrag,\n handleDrop,\n isAddRowDisabled,\n isFullScreen,\n isRemoveRowDisabled,\n isTextCategory,\n handleOnUpdateAnswer,\n handleUpdateVariantAnswer,\n handleQuickReplyChange,\n handleSelectCategory,\n handleChangeSwitch,\n handleChangeRequestAgentCheckbox,\n setCategoryColor,\n setEditor,\n setFullScreen,\n setIsTextCategory,\n setQuickReplies,\n setRequestAgent,\n setShowAdvanceSettings,\n setShowColorPicker,\n setTextCategoryValue,\n setAnswerVariants,\n showColorPicker,\n updateEditedStringAnswer,\n updateFormattedAnswer,\n answerVariants,\n textCategoryValue,\n answerEditorAnswer,\n allCategories,\n allAnswers,\n getSimilarAnswers,\n tabView,\n handleOnAddAnswer,\n handleChangeTabView,\n handleChangeDateRange,\n handleTableChange,\n handleFetchInsightFromDateFilter,\n setSessionFilteredInfo,\n sessionFilteredInfo,\n successMessage,\n answerEditorQuestions,\n answerEditorInsights,\n dateFilter,\n isCreateMode,\n isOpen,\n similarAnswers,\n shouldUseSkeletonComponent,\n parsedAnswer,\n };\n};\n\nexport default useAnswerEditor;\n","import styled from 'styled-components';\nimport Input from 'components/Input';\nimport { cssVariables } from 'styles/root';\nimport { Select } from 'antd';\nimport { StyledFlexRowLeft } from 'styles/GenericStyledComponents';\n\nexport const StyledQuickReplyLabel = styled(Input)`\n width: 100%;\n`;\n\nexport const StyledQuickReplyQuestion = styled(Input)`\n width: 100%;\n margin-left: 10px;\n`;\n\nexport const StyledQuickReplyAnswerList = styled(Select)`\n width: 100%;\n height: auto;\n\n & .ant-select-selector {\n height: auto !important;\n & .ant-select-selection-item {\n white-space: pre-wrap;\n text-overflow: ellipsis;\n overflow: inherit;\n }\n\n & .ant-select-item-option-content {\n white-space: pre-wrap;\n text-overflow: ellipsis;\n overflow: inherit;\n height: auto;\n }\n }\n\n & .ant-select-item-option-content {\n white-space: pre-wrap;\n text-overflow: ellipsis;\n overflow: inherit;\n }\n`;\n\nexport const StyledEditCheckbox = styled.div`\n display: flex;\n flex-direction: column;\n margin: auto 10px;\n align-items: center;\n font-size: 11px;\n font-weight: ${cssVariables.font.bold};\n\n > span:first-child {\n margin-top: -10px;\n }\n`;\n\nexport const StyledActionButtons = styled.div`\n display: flex;\n align-items: center;\n\n &:hover {\n cursor: pointer;\n }\n\n &.disabled {\n cursor: not-allowed;\n }\n\n > * + * {\n margin: 0px 5px;\n }\n\n .anticon:not(.anticon-close) {\n font-size: 20px;\n }\n\n .ant-btn {\n &:hover,\n &:active {\n background-color: transparent !important;\n }\n }\n`;\n\nexport const StyledDraggableWrapper = styled.div`\n margin: auto 5px 5px auto;\n`;\n\nexport const StyledAnswerLabel = styled(StyledFlexRowLeft)`\n margin-left: 0;\n width: 100%;\n display: flex;\n padding: 10px;\n cursor: pointer;\n`;\n\nexport const StyledLabel = styled(StyledFlexRowLeft)`\n width: 20%;\n display: flex;\n`;\n\nexport const StyledDeleteRowButton = styled.div`\n margin: auto auto auto 5px;\n\n &:hover {\n cursor: pointer;\n }\n\n &.disabled {\n cursor: not-allowed;\n }\n`;\n","export default __webpack_public_path__ + \"static/media/unlink-solid.4daa6348.svg\";","export default __webpack_public_path__ + \"static/media/link-solid.89385222.svg\";","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Divider } from 'antd';\n\nconst HorizontalDivider = props => {\n const { text, position } = props;\n return {text} ;\n};\n\nHorizontalDivider.propTypes = {\n text: PropTypes.string,\n position: PropTypes.oneOf(['left', 'right', 'center']),\n};\n\nexport default HorizontalDivider;\n","import { useContext, useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { DeleteFilled, EditOutlined } from '@ant-design/icons';\n\nimport {\n StyledQuickReplyLabel,\n StyledQuickReplyAnswerList,\n StyledLabel,\n StyledAnswerLabel,\n StyledDeleteRowButton,\n} from './QuickReplyFormRow.styles';\nimport { StyledFlexRowCenter } from 'styles/GenericStyledComponents';\nimport { cssVariables } from 'styles/root';\nimport { allAnswersSelector } from 'selectors/bot/answers';\nimport { Context } from 'store/store';\nimport Button from 'components/Button';\nimport UnlinkIcon from 'assets/images/unlink-solid.svg';\nimport LinkIcon from 'assets/images/link-solid.svg';\nimport HorizontalDivider from 'components/HorizontalDivider';\n\nconst QuickReplyFormRow = props => {\n const {\n deleteRow,\n index,\n isRemoveRowDisabled,\n item,\n onQuickReplyChange,\n ...rest\n } = props;\n const [state] = useContext(Context);\n const allAnswers = allAnswersSelector(state);\n\n const filterOption = (input, option) =>\n (option?.label ?? '').toLowerCase().includes(input.toLowerCase());\n\n const answerList = useMemo(() => {\n return allAnswers.map(answer => ({\n label: answer.text,\n value: answer.jid,\n }));\n }, [allAnswers, item?.answer]);\n\n const actionSelected = useMemo(() => {\n if (!item.editableQuestion && item.answer) {\n return 'Update';\n } else if (item.editableQuestion || item.answer) {\n return 'Unlink';\n } else {\n return 'Link';\n }\n }, [item.editableQuestion, item.answer]);\n\n return (\n \n
\n
\n \n Question:\n \n onQuickReplyChange(e, 'question', item.key)}\n disabled={!item.editableQuestion && item.answer}\n />\n \n Display:\n \n onQuickReplyChange(e, 'label', item.key)}\n disabled={!item.editableQuestion && item.answer}\n />\n \n ) : actionSelected === 'Unlink' ? (\n \n ) : (\n \n )\n }\n value={actionSelected}\n variant=\"link\"\n style={{ fontSize: 16 }}\n onClick={e =>\n onQuickReplyChange(\n e,\n actionSelected === 'Unlink' ? 'unlink' : 'edit',\n item.key\n )\n }\n size=\"small\"\n />\n \n deleteRow(item.key)}\n />\n \n \n {(actionSelected === 'Unlink' || actionSelected === 'Update') && (\n
\n \n Linked Answer:\n \n onQuickReplyChange(e, 'answer', item.key)}\n options={answerList}\n filterOption={filterOption}\n showSearch\n hidden={!item.editableQuestion}\n />\n onQuickReplyChange(e, 'edit', item.key)}\n >\n {allAnswers?.find(answer => answer.jid === item.answer)?.text}\n \n \n )}\n
\n );\n};\n\nQuickReplyFormRow.propTypes = {\n deleteRow: PropTypes.func,\n index: PropTypes.number,\n isRemoveRowDisabled: PropTypes.bool,\n item: PropTypes.object.isRequired,\n onQuickReplyChange: PropTypes.func.isRequired,\n};\n\nexport default QuickReplyFormRow;\n","import styled from 'styled-components';\nimport QuickReplyFormRow from 'components/QuickReplyFormRow';\n\nexport const StyledInputGroup = styled(QuickReplyFormRow)`\n align-items: center;\n transition: transform 0.2s ease-in-out;\n width: 100%;\n padding: 5px 0;\n\n & .unlinkedIcon {\n width: 15px;\n height: 17px;\n\n margin-right: 5px;\n }\n & .linkedIcon {\n width: 20px;\n height: 20px;\n margin-right: 5px;\n }\n & .ant-input {\n padding: 5px;\n height: 32px;\n }\n\n & input-with-label {\n width: 100%;\n }\n`;\n\nexport const StyledInputGroupLabel = styled.div`\n display: flex;\n & span {\n width: 100%;\n margin-left: 19px;\n }\n\n & span:first-child {\n width: 60%;\n }\n`;\n","import React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { Link } from 'react-router-dom';\nimport { PlusOutlined } from '@ant-design/icons';\n\nimport { StyledInputGroup } from './QuickReply.styles';\nimport { StyledFlexColumn } from 'styles/GenericStyledComponents';\n\nconst QuickReply = ({\n addRow,\n deleteRow,\n handleQuickReplyChange,\n isAddRowDisabled,\n isRemoveRowDisabled,\n quickReplies,\n}) => {\n const MemoisedQuickReplyForm = useMemo(() => {\n return (quickReplies?.quickReplyOptions || []).map((item, idx) => (\n \n ));\n }, [quickReplies.quickReplyOptions]);\n\n return (\n \n {quickReplies?.quickReplyOptions?.length ? MemoisedQuickReplyForm : null}\n \n {'Add more'}\n \n \n );\n};\n\nQuickReply.propTypes = {\n addRow: PropTypes.func.isRequired,\n deleteRow: PropTypes.func.isRequired,\n quickReplies: PropTypes.object.isRequired,\n handleQuickReplyChange: PropTypes.func.isRequired,\n isAddRowDisabled: PropTypes.bool,\n isRemoveRowDisabled: PropTypes.bool,\n};\n\nexport default QuickReply;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';\nimport styled from 'styled-components';\nimport { cssVariables } from 'styles/root';\n\nconst StyledWrapper = styled.div`\n background-color: ${props => props.color || cssVariables.gray2};\n padding: 5px 12px;\n display: flex;\n align-items: center;\n border-radius: 5px;\n font-size: 12px;\n\n .anticon {\n margin-left: 5px;\n }\n\n :hover {\n cursor: pointer;\n background-color: ${cssVariables.primaryBlueHover};\n color: ${props => props.color || cssVariables.primaryBlue};\n }\n`;\n\nconst ToggleFullScreenButton = ({\n isFullScreen,\n setFullScreen,\n color,\n ...rest\n}) => {\n return (\n <>\n {isFullScreen ? (\n setFullScreen(false)}\n color={color}\n {...rest}\n >\n {'Exit Fullscreen'} \n \n \n ) : (\n setFullScreen(true)}\n color={color}\n {...rest}\n >\n {'Enter Fullscreen'} \n \n \n )}\n >\n );\n};\n\nToggleFullScreenButton.propTypes = {\n color: PropTypes.string,\n isFullScreen: PropTypes.bool,\n setFullScreen: PropTypes.func.isRequired,\n};\n\nexport default ToggleFullScreenButton;\n","import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport {\n FlexColWrapper,\n FlexRowWrapper,\n StyledDeleteIcon,\n} from './Variant.styles';\nimport Select from 'components/Select';\n\nconst VariantConditionForm = ({\n form,\n handleDelete,\n isOnlyForm,\n isFirstRow,\n}) => {\n const variantOptions1 = [\n {\n label: 'Option 1',\n value: 'option1',\n },\n {\n label: 'Option 2',\n value: 'option2',\n },\n {\n label: 'Option 3',\n value: 'option3',\n },\n ];\n\n const variantOptions2 = [\n {\n label: 'Option 1',\n value: 'option1',\n },\n {\n label: 'Option 2',\n value: 'option2',\n },\n {\n label: 'Option 3',\n value: 'option3',\n },\n ];\n\n const tagOptions = ['Apples', 'Nails', 'Bananas', 'Helicopters'];\n\n const [selectedItems, setSelectedItems] = useState([]);\n const filteredOptions = tagOptions.filter(o => !selectedItems.includes(o));\n\n return (\n \n \n \n {isFirstRow ? IF : AND }\n \n \n \n \n \n handleDelete(form.key, isOnlyForm)}\n color=\"red\"\n />\n \n );\n};\n\nVariantConditionForm.propTypes = {\n form: PropTypes.object,\n handleDelete: PropTypes.func.isRequired,\n isOnlyForm: PropTypes.bool,\n isFirstRow: PropTypes.bool,\n};\n\nexport default VariantConditionForm;\n","import React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport RichTextEditor from 'components/RichTextEditor';\nimport CodeMirror from './CodeMirror';\nimport { DEFAULT_EDITOR } from 'constants/answerbank/defaults';\nimport {\n StyledRCEEditor,\n StyledRCETip,\n StyledHTMLEditorWrapper,\n} from './Editor.styles';\nimport ChatBubble from 'components/ChatBubble';\n\nconst Editor = props => {\n const {\n answerId,\n editor,\n editedHTML,\n editedRichTextAnswer,\n updateFormattedAnswer,\n isFullScreen,\n isADefaultAnswer,\n ...rest\n } = props;\n const RichText = useMemo(() => {\n return (\n \n \n ({`Please prefix your links with`} {`https://`} or{' '}\n {`http://`} {' '}\n {`if you want the user to be redirected outside\n your page.`}\n )\n \n \n \n );\n }, [editor, editedRichTextAnswer, answerId, isADefaultAnswer]);\n\n const HTMLEditor = useMemo(() => {\n return (\n \n \n \n );\n }, [editor, editedHTML, answerId]);\n\n const Preview = useMemo(() => {\n return (\n \n );\n }, [editor, editedHTML, answerId]);\n\n return editor === DEFAULT_EDITOR\n ? RichText\n : editor === 'preview'\n ? Preview\n : HTMLEditor;\n};\n\nEditor.defaultProps = {\n editor: DEFAULT_EDITOR,\n};\n\nEditor.propTypes = {\n answerId: PropTypes.string,\n editor: PropTypes.string.isRequired,\n editedHTML: PropTypes.string,\n editedRichTextAnswer: PropTypes.string,\n isFullScreen: PropTypes.bool,\n isADefaultAnswer: PropTypes.bool,\n answerWithOutLineBreaker: PropTypes.array,\n editedPreviewFormat: PropTypes.array,\n};\n\nexport default Editor;\n","import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { FlexColWrapper, FlexRowWrapper } from './Variant.styles';\nimport VariantConditionForm from './VariantConditionForm';\nimport { DEFAULT_EDITOR, EDITOR_OPTIONS } from 'constants/answerbank/defaults';\nimport Select from 'components/Select';\nimport { PlusCircleTwoTone } from '@ant-design/icons';\nimport Editor from 'components/EditorAndViewer/Editor/Editor';\n\nconst Variants = ({\n answerId,\n variant,\n editor,\n editedHTML,\n editedRichTextAnswer,\n formFields,\n onUpdateVariant,\n}) => {\n const [variantEditor, setVariantEditor] = useState(editor);\n const DEFAULT_ANSWER_VARIANT = {\n forms: [\n {\n key: variant.forms.length + 1 || 1,\n select1: '',\n select2: '',\n select3: [''],\n },\n ],\n displayAnswer: {\n html: '',\n rce: '',\n },\n editor: DEFAULT_EDITOR,\n key: variant.key + 1,\n };\n\n const handleUpdateVariantEditor = e => {\n setVariantEditor(e);\n onUpdateVariant({ ...variant, editor: e });\n };\n\n const handleAddFormField = () => {\n onUpdateVariant({\n ...variant,\n forms: [...variant.forms, ...DEFAULT_ANSWER_VARIANT.forms],\n });\n };\n\n const handleDeleteFormField = (formKey, isOnlyForm) => {\n if (isOnlyForm) {\n onUpdateVariant(variant, isOnlyForm);\n } else {\n const updatedForms = variant.forms.filter(f => f.key !== formKey);\n onUpdateVariant({\n ...variant,\n forms: updatedForms,\n });\n }\n };\n\n return (\n \n \n {formFields.map((f, idx) => {\n return (\n \n );\n })}\n \n \n \n \n \n \n \n {}}\n updateFormattedAnswer={() => {}}\n />\n \n );\n};\n\nVariants.propTypes = {\n answerId: PropTypes.string.isRequired,\n editor: PropTypes.string.isRequired,\n editedHTML: PropTypes.string,\n editedRichTextAnswer: PropTypes.string,\n formFields: PropTypes.array.isRequired,\n onUpdateVariant: PropTypes.func.isRequired,\n variant: PropTypes.object.isRequired,\n};\n\nexport default Variants;\n","import React from 'react';\nimport {\n Checkbox,\n Popover,\n Tag,\n Switch,\n Tooltip,\n Tabs,\n Skeleton,\n Table,\n} from 'antd';\nimport PropTypes from 'prop-types';\nimport {\n CaretDownFilled,\n CaretRightFilled,\n CheckCircleOutlined,\n DislikeTwoTone,\n EditOutlined,\n FileTextOutlined,\n LikeTwoTone,\n PlusOutlined,\n SaveOutlined,\n SyncOutlined,\n WarningFilled,\n} from '@ant-design/icons';\nimport moment from 'moment';\n\nimport {\n CategoryFieldWrapper,\n StyledAnswerEditor,\n StyledAddVariantButton,\n StyledSectionContent,\n StyledErrorMsg,\n StyledQuickBtnToggleOption,\n StyledQuestionsWrapper,\n} from './AnswerEditor.styles';\nimport Modal from 'components/Modals/GenericModal';\nimport TextArea from 'components/TextArea';\nimport Button from 'components/Button';\nimport CategorySelect from 'components/CategorySelect';\nimport {\n ANSWER_TIP,\n DISPLAY_ANSWER_TIP,\n DEFAULT_ANSWER_VERSION,\n MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION,\n} from 'constants/answerbank/defaults';\nimport useAnswerEditor from './hooks';\nimport Input from 'components/Input';\nimport { ChromePicker } from 'react-color';\nimport QuickReply from './QuickRelpy/QuickReply';\nimport TitleToolTip from 'components/ToolTips/TitleToolTip';\nimport ToggleFullScreenButton from 'components/Button/ToggleFullScreenButton';\nimport Variants from './Variant/Variants';\nimport HRWithCenteredText from 'components/HR/HRWithCenteredText';\nimport { cssVariables } from 'styles/root';\nimport { StyledAnswersItem } from '../ImportAnswerModal/ImportAnswerModal.style';\nimport { StyledAnswers } from 'pages/BotDetails/AnswerBank/SimilarAnswerModal/SimilarAnswerModal.style';\nimport {\n FlexRowSpaceBetweenWrapper,\n FlexRowWrapper,\n} from './Variant/Variant.styles';\nimport Question from 'components/Question';\nimport {\n StyledFlexColumn,\n StyledFlexRowCenter,\n StyledFlexRowLeft,\n StyledSpacEvenlyFlexRow,\n StyledSpaceBetweenFlexCenter,\n StyledSpaceBetweenFlexColumn,\n StyledWarningBox,\n} from 'styles/GenericStyledComponents';\nimport { useMemo } from 'react';\nimport Alert from 'components/Alert';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\nimport { TOOLTIPS } from 'constants/analytics';\nimport {\n ChartWrapper,\n DataSummaryCard,\n StyledColumnChart,\n StyledQuestion,\n} from 'pages/BotDetails/Analytics/Analytics.styles';\nimport { UTCToLocal, getLocalTimeString } from 'utils/dates';\nimport DateFilter from 'components/DateFilter';\nimport EditorAndViewer from 'components/EditorAndViewer/EditorAndViewer';\n\nconst showAnswerVariant = process.env.REACT_APP_SHOW_ANSWER_VARIANT;\nconst smallFont = { fontSize: 12 };\nconst largeFont = { fontSize: 20 };\n\nconst HIGH_SIMILARITY_ANSWER_TOOLTIP_TITLE =\n 'This answer has high similarity score to the current answer';\nconst HIGH_SIMILARITY_SCORE_TOOLTIP_TITLE = 'High similarity score';\nconst HIGH_SIMILARITY_TOGGLE_TOOLTIP_TITLE =\n 'Similar answer with high similary score detected. Please EDIT anchor answer or save answer as DRAFT';\nconst { TabPane } = Tabs;\n\nconst AnswerEditor = props => {\n const {\n isTemp,\n title,\n answerType,\n onAddAnswer,\n onAddDraftAnswer,\n setNewAnswer,\n pageLoading,\n ...rest\n } = props;\n const {\n answer,\n categoryColor,\n confirmLoading,\n editedHTML,\n editedRichTextAnswer,\n editedStringAnswer,\n editor,\n error,\n jid,\n quickReplies,\n requestAgent,\n selectedCategory,\n showAdvanceSettings,\n addRow,\n deleteRow,\n handleAddAnswerVariant,\n handleClose,\n handleDrag,\n handleDrop,\n handleUpdateVariantAnswer,\n isAddRowDisabled,\n isFullScreen,\n isRemoveRowDisabled,\n isTextCategory,\n handleOnUpdateAnswer,\n handleQuickReplyChange,\n handleSelectCategory,\n handleChangeRequestAgentCheckbox,\n setCategoryColor,\n setEditor,\n setFullScreen,\n setIsTextCategory,\n setQuickReplies,\n setRequestAgent,\n setShowAdvanceSettings,\n setShowColorPicker,\n setTextCategoryValue,\n showColorPicker,\n updateEditedStringAnswer,\n updateFormattedAnswer,\n textCategoryValue,\n handleChangeSwitch,\n answerVariants,\n answerEditorAnswer,\n allCategories,\n getSimilarAnswers,\n handleOnAddAnswer,\n handleChangeTabView,\n handleChangeDateRange,\n handleTableChange,\n handleFetchInsightFromDateFilter,\n sessionFilteredInfo,\n setSessionFilteredInfo,\n answerEditorQuestions,\n answerEditorInsights,\n dateFilter,\n similarAnswers,\n tabView,\n isOpen,\n shouldUseSkeletonComponent,\n isCreateMode,\n parsedAnswer,\n } = useAnswerEditor({\n onAddDraftAnswer,\n setNewAnswer,\n });\n const isFinalAnswer =\n answerEditorAnswer?.version === 'final'\n ? true\n : false || answer?.version === DEFAULT_ANSWER_VERSION;\n\n const isCloneAnswer = answer?.clone;\n const isAnswerView = (tabView && tabView === 'answer') || !tabView;\n const draftToFinalAnswerDisabled =\n !isFinalAnswer && answerEditorAnswer.isVersionToggleDisabled ? true : false;\n const skipSimilarityCheck = useMemo(() => {\n // add new answer:\n // always check for similarity\n // when answer in edited\n // call similarity check\n if (editedStringAnswer?.length && editedStringAnswer !== answer?.text) {\n return false;\n }\n // if similar answers is found\n // and answer inside textarea is not change\n // skip similarity check\n if (\n similarAnswers?.length &&\n (editedStringAnswer === answer?.text ||\n (!editedStringAnswer && answer.text))\n ) {\n return true;\n }\n // if similar answers is found\n // and answer inside textarea has changed\n // call similarity check\n else if (similarAnswers?.length && editedStringAnswer !== answer?.text) {\n return false;\n }\n // if no similar answers found\n // skip similarity check\n else if (!similarAnswers?.length) {\n return true;\n }\n // always call similarity check for all\n // other scenarios\n return false;\n }, [similarAnswers?.length, editedStringAnswer, answer?.text]);\n\n const renderVariants = () => {\n return (\n \n \n {answerVariants.map((i, idx) => {\n return (\n <>\n \n \n >\n );\n })}\n \n );\n };\n const renderModalTitle = () => (\n <>\n \n {isCloneAnswer ? 'Clone Answer' : title || 'Edit Your Answer'}\n \n {isFinalAnswer ? 'Final' : 'Draft'}{' '}\n {isFinalAnswer ? : }\n \n \n \n >\n );\n\n const renderQuestionsTab = () => {\n if (shouldUseSkeletonComponent) {\n return ;\n }\n return (\n <>\n {!answerEditorQuestions?.length ? (\n {'No linked questions found'} \n ) : (\n \n {'Linked Questions:'} \n \n {answerEditorQuestions.map(q => {\n return (\n \n );\n })}\n \n \n )}\n >\n );\n };\n\n const allQuestionsTableData = [\n {\n title: 'Sessions',\n align: 'center',\n dataIndex: 'sessionID',\n width: '10%',\n sorter: {},\n filteredValue: sessionFilteredInfo || null,\n onFilter: (value, row) => {\n if (value === 'undefined') {\n return !row.sessionID;\n } else {\n return row.sessionID && row.sessionID.includes(value);\n }\n },\n render: (sessionID, row) => {\n if (sessionID) {\n return (\n \n );\n } else {\n return 'N/A';\n }\n },\n },\n {\n title: `Question (${\n answerEditorInsights?.table?.allQuestions?.length || 0\n })`,\n dataIndex: 'question',\n width: '45%',\n render: (question, row) => {\n return (\n \n {question} \n \n );\n },\n },\n {\n title: 'Visitor ID',\n align: 'center',\n dataIndex: 'visitorID',\n sorter: {},\n render: visitorID => (\n {!!visitorID ? visitorID : 'N/A'}
\n ),\n },\n {\n title: 'Feedback',\n dataIndex: 'feedback',\n align: 'center',\n width: '10%',\n render: feedback => {\n if (feedback === 1) {\n return ;\n } else if (feedback === 0) {\n return ;\n } else {\n return null;\n }\n },\n },\n {\n title: 'Date',\n align: 'center',\n dataIndex: 'date',\n width: '20%',\n defaultSortOrder: 'ascend',\n sorter: {},\n render: date => {\n const localDate = UTCToLocal(new Date(date));\n return {getLocalTimeString(localDate)}
;\n },\n },\n ];\n\n const graphConfig = useMemo(() => {\n return {\n data: answerEditorInsights?.graph || [],\n xField: 'date',\n yField: 'count',\n xAxis: {\n label: {\n autoRotate: false,\n },\n },\n };\n }, [answerEditorInsights?.graph]);\n\n const renderInsightsTab = () => {\n if (shouldUseSkeletonComponent || !answerEditorInsights) {\n return ;\n }\n return (\n \n \n \n \n \n {answerEditorInsights?.aggregations?.totalQuestions ? (\n <>\n \n \n \n {`# of sessions`}
\n \n {answerEditorInsights?.aggregations?.sessionCount || 0}\n \n
\n \n \n\n \n \n {`# of visitors`}
\n \n {answerEditorInsights?.aggregations?.visitorCount || 0}\n \n
\n \n \n\n \n \n {`# of questions asked`}
\n \n {answerEditorInsights?.aggregations?.totalQuestions || 0}\n \n
\n \n \n \n\n \n \n {`Total questions asked from ${moment(\n dateFilter.startDate\n ).format('MMMM D, YYYY')} - ${moment(dateFilter.endDate).format(\n 'MMMM D, YYYY'\n )}`} \n \n \n \n \n \n \n >\n ) : (\n {'No interaction found.'} \n )}\n \n );\n };\n\n const renderAdvanceSettings = () => {\n if (showAdvanceSettings && !isCreateMode) {\n return (\n \n \n \n \n \n \n \n \n \n {\n setQuickReplies({\n ...quickReplies,\n quickReply: !quickReplies.quickReply,\n });\n setRequestAgent(false);\n }}\n />{' '}\n \n {`Quick Reply`}\n \n \n {' '}\n \n {`Request Callback`}\n \n \n {quickReplies.quickReply ? (\n \n ) : null}\n {answerVariants && answerVariants.length ? renderVariants() : null}\n {showAnswerVariant && showAnswerVariant !== 'false' ? (\n \n {'Add Variant'} \n \n >\n }\n />\n ) : null}\n \n );\n }\n\n return
;\n };\n\n const renderAnswerTab = () => {\n return (\n \n \n {draftToFinalAnswerDisabled ? (\n \n \n \n ) : null}\n \n \n {answer?.lastEdited && (\n \n Last updated: \n {answer?.lastEdited}\n \n )}\n \n {!!error && }\n \n \n \n\n {answerEditorAnswer?.text !== editedStringAnswer && !isCreateMode ? (\n \n \n {` Anchor answer will not be directly reflected as the answer to the clients. Updating \"Display Answer\" will do.`}\n \n ) : null}\n {!shouldUseSkeletonComponent && similarAnswers?.length ? (\n \n \n Similar Answers: \n \n {similarAnswers.map(item => (\n \n MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION\n ? HIGH_SIMILARITY_SCORE_TOOLTIP_TITLE\n : 'Similarity Score'\n }\n >\n MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION\n ? cssVariables.red10\n : null\n }\n >\n {item.score?.toFixed(3)}\n \n \n \n {item.score > MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION ? (\n \n \n {item.answer.context.text}\n \n \n ) : (\n {item.answer.context.text} \n )}\n \n \n ))}\n \n ) : shouldUseSkeletonComponent ? (\n \n ) : null}\n \n {isTemp && !isCreateMode ? (\n \n setTextCategoryValue(e.target.value)}\n full\n />\n setShowColorPicker(false)}\n >\n setCategoryColor(color.hex)}\n />\n \n }\n trigger=\"click\"\n visible={showColorPicker}\n onVisibleChange={() => setShowColorPicker(!showColorPicker)}\n >\n \n \n \n ) : null}\n \n {allCategories.length && !isCreateMode ? (\n \n \n \n ) : null}\n {!isCreateMode && (\n \n \n ) : (\n \n )\n }\n variant=\"link\"\n size=\"medium\"\n value={\n !showAdvanceSettings\n ? 'Show advanced settings'\n : 'Hide advanced settings'\n }\n onClick={() => setShowAdvanceSettings(!showAdvanceSettings)}\n />\n \n )}\n \n {renderAdvanceSettings()}\n \n );\n };\n\n return (\n \n isCreateMode\n ? handleOnAddAnswer(skipSimilarityCheck)\n : isAnswerView\n ? handleOnUpdateAnswer(isTemp)\n : handleClose()\n }\n confirmLoading={confirmLoading}\n width={isFullScreen && '100%'}\n isFullScreen={isFullScreen}\n isFormModal\n spinning={confirmLoading}\n okText={\n !isAnswerView ? (\n <>\n {'OK'} \n >\n ) : undefined\n }\n footer={\n isAnswerView ? (\n \n
\n \n \n \n }\n value={'Revalidate Answer'}\n variant=\"link\"\n size=\"small\"\n disabled={shouldUseSkeletonComponent || confirmLoading}\n onClick={() => getSimilarAnswers(answer?.version)}\n />\n \n\n
\n \n : \n }\n onClick={() =>\n isCreateMode\n ? handleOnAddAnswer(skipSimilarityCheck)\n : handleOnUpdateAnswer(isTemp)\n }\n />\n
\n
\n ) : undefined\n }\n >\n \n {isCreateMode ? (\n renderAnswerTab()\n ) : (\n <>\n \n \n {renderAnswerTab()}\n \n \n {renderQuestionsTab()}\n \n \n {renderInsightsTab()}\n \n \n >\n )}\n \n \n );\n};\n\nAnswerEditor.propTypes = {\n onUpdateAnswer: PropTypes.func,\n answerEditorAnswer: PropTypes.object,\n isTemp: PropTypes.bool,\n onAddAnswer: PropTypes.func,\n onAddDraftAnswer: PropTypes.func,\n setNewAnswer: PropTypes.func,\n};\n\nexport default AnswerEditor;\n","import { useContext, useState } from 'react';\nimport { message } from 'antd';\nimport { useLocation } from 'react-router-dom';\n\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport {\n getAllQuestionsWithAnswerSelector,\n questionEditorSelector,\n} from 'selectors/bot';\nimport { getTokenSelector } from 'selectors/user';\nimport { apiService } from 'services/api.service';\nimport {\n ADD_QUESTION,\n CLOSE_QUESTION_EDITOR,\n PUSH_AS_QUESTION_EDITOR_SELECTED_ANSWER,\n REMOVE_HARD_LINK_FROM_QUESTION_EDITOR,\n SHOW_RESPONSE_PICKER_MODAL_FOR_QUESTIONS,\n SHOW_RESPONSE_PICKER_MODAL_WITH_ANSWERSCORE_PAYLOAD,\n UPDATE_QUESTION_AND_ANSWER,\n UPDATE_QUESTION_EDITOR,\n} from 'store/action';\nimport { Context } from 'store/store';\nimport { withPrefixUUID } from 'utils';\nimport { allAnswersSelector } from 'selectors/bot/answers';\nimport useSelector from 'store/useSelector';\n\nconst useQuestionEditor = () => {\n const token = useSelector(getTokenSelector);\n const questionEditorModal = useSelector(questionEditorSelector);\n const allQuestions = useSelector(getAllQuestionsWithAnswerSelector);\n const allAnswers = useSelector(allAnswersSelector);\n const { pathname } = useLocation();\n const [state, dispatch] = useContext(Context);\n const {\n sentinel,\n bot: { jid, name },\n } = state;\n\n const WITH_PREFIX_BOT_JID = withPrefixUUID(jid || pathname);\n const [loading, setLoading] = useState(false);\n const [newQuestion, setNewQuestion] = useState(questionEditorModal.text);\n const [showSkeleton, setShowSkeleton] = useState(false);\n const [currentStep, setCurrentStep] = useState(0);\n const [disabledNextStep, setDisabledNextStep] = useState(false);\n const [version, setVersion] = useState(null);\n const [showDisplayAnswer, setShowDisplayAnswer] = useState(false);\n\n const handleClose = () => {\n setShowSkeleton(false);\n setNewQuestion(null);\n setCurrentStep(0);\n dispatch({\n type: CLOSE_QUESTION_EDITOR,\n });\n };\n\n const handleChangeText = evt => {\n setNewQuestion(evt.target.value);\n if (!evt.target.value) {\n setDisabledNextStep(true);\n } else if (disabledNextStep) {\n setDisabledNextStep(false);\n }\n };\n\n const pushAsQuestionEditorAnswer = answerId => {\n dispatch({\n type: PUSH_AS_QUESTION_EDITOR_SELECTED_ANSWER,\n payload: {\n answerId,\n answersWithScore: questionEditorModal?.answersWithScore,\n },\n });\n };\n\n const handleSaveQuestion = async saveAsDraft => {\n try {\n setLoading(true);\n const { action, text, jid, answerId, answersWithScore } =\n questionEditorModal;\n\n if (action === 'add') {\n const res = await apiService.hardLinkQuestion(\n newQuestion || text,\n answerId || null,\n // use bot jid when creating new answer\n WITH_PREFIX_BOT_JID,\n saveAsDraft ? 'draft' : 'final',\n sentinel,\n token,\n true\n );\n\n if (!res.data.success) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n await dispatch({\n type: ADD_QUESTION,\n payload: res.data.report[0],\n });\n } else {\n const res = await apiService.editQuestion(\n newQuestion || text,\n answerId,\n // use question jid when editing new answer\n jid,\n saveAsDraft ? 'draft' : 'final',\n sentinel,\n token,\n true\n );\n\n if (!res.data.success) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n\n await dispatch({\n type: UPDATE_QUESTION_AND_ANSWER,\n payload: res.data.report[0],\n });\n }\n message.success('Successfully saved question');\n handleClose();\n setLoading(false);\n } catch (error) {\n setLoading(false);\n message.error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n setLoading(false);\n };\n\n const handleUnlinkQuestion = async () => {\n setLoading(true);\n try {\n await apiService.deleteQuestionHardLink(\n questionEditorModal.jid,\n sentinel,\n token\n );\n setLoading(false);\n dispatch({\n type: REMOVE_HARD_LINK_FROM_QUESTION_EDITOR,\n payload: questionEditorModal.jid,\n });\n return message.success(\n 'Successfully unlinked the question to the answer'\n );\n } catch (error) {\n setLoading(false);\n return message.error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleClickLinkAnswer = async skipSimilarityCheck => {\n // NOTE:\n // questionEditorModal.text => text from source\n // (sources: textarea OR sidebar user question OR question from question list)\n // questionEditorModal.newQuestion => text after any reducer is called to update questionEditorModal\n // newQuestion => question state on a component level; editable; changes when textArea value is changed\n setLoading(true);\n try {\n if (\n questionEditorModal.action === 'edit' &&\n questionEditorModal.answerId\n ) {\n setCurrentStep(currentStep + 1);\n setShowSkeleton(true);\n const res =\n await apiService.getAnswersWithScoreFromQuestionWithLinkedAnswer(\n newQuestion ||\n questionEditorModal.newQuestion ||\n questionEditorModal.text,\n questionEditorModal.answerId,\n WITH_PREFIX_BOT_JID,\n sentinel,\n token\n );\n if (res.data.report[0]?.length) {\n await dispatch({\n type: UPDATE_QUESTION_EDITOR,\n payload: {\n answersWithScore: res.data.report[0],\n newQuestion,\n answerId: questionEditorModal.answerId,\n },\n });\n }\n } else if (\n questionEditorModal.action === 'add' ||\n (questionEditorModal.action === 'edit' && !questionEditorModal.answerId)\n ) {\n setCurrentStep(currentStep + 1);\n setShowSkeleton(true);\n const res = await apiService.getAnswersWithScoreFromQuestion(\n newQuestion ||\n questionEditorModal.newQuestion ||\n questionEditorModal.text,\n WITH_PREFIX_BOT_JID,\n sentinel,\n token\n );\n if (res.data.report[0]?.length) {\n await dispatch({\n type: UPDATE_QUESTION_EDITOR,\n payload: {\n answersWithScore: res.data?.report[0],\n newQuestion,\n },\n });\n }\n }\n setShowSkeleton(false);\n return setLoading(false);\n } catch (error) {\n message.error(error.message || DEFAULT_ERROR_MESSAGE);\n setLoading(false);\n }\n };\n\n const handleChooseNewLinkedAnswer = () => {\n dispatch({\n type: SHOW_RESPONSE_PICKER_MODAL_WITH_ANSWERSCORE_PAYLOAD,\n payload: {\n answers: allAnswers,\n botName: name,\n withQuestion: true,\n isHardLinked: true,\n action: 'edit',\n answerId: questionEditorModal.answer.jid,\n itemToChange: {\n question: questionEditorModal,\n answer: questionEditorModal.answer,\n },\n },\n });\n };\n\n const handleChooseNewquestion = () => {\n dispatch({\n type: SHOW_RESPONSE_PICKER_MODAL_FOR_QUESTIONS,\n payload: {\n questions: allQuestions,\n action: 'edit',\n botName: name,\n editableQuestion: true,\n isHardLinked: true,\n questionId: questionEditorModal.jid,\n answer: questionEditorModal.answer,\n },\n });\n };\n\n return {\n currentStep,\n disabledNextStep,\n loading,\n questionEditorModal,\n newQuestion,\n showSkeleton,\n showDisplayAnswer,\n version,\n handleClose,\n handleChangeText,\n handleChooseNewLinkedAnswer,\n handleClickLinkAnswer,\n handleSaveQuestion,\n handleUnlinkQuestion,\n pushAsQuestionEditorAnswer,\n setCurrentStep,\n setShowDisplayAnswer,\n setVersion,\n };\n};\n\nexport default useQuestionEditor;\n","import React from 'react';\nimport styled from 'styled-components';\nimport { Skeleton, Steps, Tag, Tooltip } from 'antd';\nimport FlipMove from 'react-flip-move';\n\nimport Modal from '../GenericModal';\nimport useQuestionEditor from './hooks';\nimport TextArea from 'components/TextArea';\nimport { FlexRowWrapper } from '../AnswerEditor/Variant/Variant.styles';\nimport { StyledAnswersItem } from 'pages/BotDetails/AnswerBank/SimilarAnswerModal/SimilarAnswerModal.style';\nimport { StyledAnswers } from '../ImportAnswerModal/ImportAnswerModal.style';\nimport {\n StyledFlexRowRight,\n StyledSpaceBetweenFlexCenter,\n UnpaddedList,\n} from 'styles/GenericStyledComponents';\nimport { useMemo } from 'react';\nimport Button from 'components/Button';\nimport AnswerWithScore from 'components/AnswerWithScore';\nimport ToggleShowDisplayAnswer from 'components/Button/ToggleShowDisplayAnswer';\nimport { StyledFlexColumn } from 'styles/GenericStyledComponents';\n\nconst { Step } = Steps;\n\nconst QuestionEditor = props => {\n const { ...rest } = props;\n const {\n currentStep,\n disabledNextStep,\n loading,\n questionEditorModal,\n newQuestion,\n showSkeleton,\n showDisplayAnswer,\n handleChangeText,\n handleClose,\n handleClickLinkAnswer,\n handleSaveQuestion,\n handleUnlinkQuestion,\n pushAsQuestionEditorAnswer,\n setCurrentStep,\n setShowDisplayAnswer,\n } = useQuestionEditor();\n\n const {\n action,\n isOpen,\n similarQuestions,\n text,\n answersWithScore,\n answersHasChanged,\n isHardLinked,\n answerId,\n jid,\n } = questionEditorModal;\n\n const skipSimilarityCheck = useMemo(() => {\n // if similar questions is found\n // and question inside textarea is not change\n // or no similar questions found\n // skip similarity check\n if (\n (similarQuestions?.length &&\n (questionEditorModal?.newQuestion === newQuestion ||\n text === newQuestion ||\n !newQuestion)) ||\n (!disabledNextStep && !newQuestion && !similarQuestions?.length) ||\n (similarQuestions?.length && !newQuestion && action === 'add')\n ) {\n return true;\n }\n // add new question:\n // always check for similarity\n // when question is edited\n // call similarity check\n else if (newQuestion !== text) {\n return false;\n }\n // if similar questions is found\n // and question inside textarea has changed\n // call similarity check\n else if (similarQuestions?.length && newQuestion !== text) {\n return false;\n }\n // if no similar questions found\n // skip similarity check\n else if (!similarQuestions?.length) {\n return true;\n }\n // always call similarity check for all\n // other scenarios\n return false;\n }, [similarQuestions?.length, newQuestion, text]);\n\n const steps = [\n {\n title: action === 'add' ? 'Add Question' : 'Edit Question',\n },\n {\n title: 'Link Answer',\n },\n ];\n\n return (\n \n {currentStep ? (\n setCurrentStep(currentStep - 1)}\n />\n ) : null}\n \n \n handleSaveQuestion(true)}\n />\n {Boolean(currentStep && isHardLinked) ? null : (\n \n !currentStep ? handleClickLinkAnswer() : handleSaveQuestion()\n }\n />\n )}\n \n ,\n ]}\n >\n setCurrentStep(val)}>\n {steps.map((step, idx) => (\n \n ))}\n \n \n \n {'Question'}: \n \n \n {questionEditorModal?.version}\n \n \n \n \n
\n {!currentStep ? (\n <>\n {showSkeleton ? : null}\n {!similarQuestions?.length &&\n skipSimilarityCheck &&\n questionEditorModal?.newQuestion &&\n questionEditorModal?.newQuestion === text ? (\n 'No Similar Questions Found'\n ) : similarQuestions?.length ? (\n <>\n
\n {'Similar Questions'} \n >\n ) : null}\n {similarQuestions?.map(item => (\n \n \n {item.score?.toFixed(3)} \n \n \n {{item.text} }\n \n \n ))}\n >\n ) : (\n <>\n {showSkeleton ? : null}\n {answersWithScore?.length ? (\n \n setShowDisplayAnswer(!showDisplayAnswer)}\n />\n \n \n {answersWithScore.map((answer, index) => (\n // react-flip-move requires list element\n \n \n pushAsQuestionEditorAnswer(answerId, 'selected')\n }\n handleUnlinkQuestion={handleUnlinkQuestion}\n showDisplayAnswer={showDisplayAnswer}\n toolTipText=\"Link Answer\"\n />\n \n ))}\n \n \n \n ) : (\n \n {'No valid answer found. Please check your answer bank.'}\n \n )}\n >\n )}\n \n );\n};\n\nQuestionEditor.propTypes = {};\n\nexport default QuestionEditor;\n","import { useState, useContext, useCallback } from 'react';\nimport { useNodesState, useEdgesState } from 'react-flow-renderer';\nimport dagre from 'dagre';\nimport { uuid } from 'uuidv4';\n\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst nodeWidth = 172;\nconst nodeHeight = 36;\nconst dagreGraph = new dagre.graphlib.Graph();\n\ndagreGraph.setDefaultEdgeLabel(() => ({}));\n\nconst getLayoutedElements = (nodes, edges, direction = 'LR') => {\n const isHorizontal = direction === 'LR';\n dagreGraph.setGraph({ rankdir: direction });\n\n nodes.forEach(node => {\n dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n });\n\n edges.forEach(edge => {\n dagreGraph.setEdge(edge.source, edge.target);\n });\n\n dagre.layout(dagreGraph);\n\n nodes.forEach(node => {\n const nodeWithPosition = dagreGraph.node(node.id);\n node.targetPosition = isHorizontal ? 'left' : 'top';\n node.sourcePosition = isHorizontal ? 'right' : 'bottom';\n\n node.position = {\n x: nodeWithPosition.x - nodeWidth / 2,\n y: nodeWithPosition.y - nodeHeight / 2,\n };\n\n return node;\n });\n return { nodes, edges };\n};\n\nconst useConversationSimulator = () => {\n const [state] = useContext(Context);\n const token = useSelector(getTokenSelector);\n const {\n bot: { jid },\n } = state;\n const [loading, setLoading] = useState(false);\n const [question, setQuestion] = useState('');\n const [initialNodes, setNodes, onNodesChange] = useNodesState([]);\n const [initialEdges, setEdges, onEdgesChange] = useEdgesState([]);\n\n const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(\n initialNodes,\n initialEdges\n );\n const { sentinel } = state;\n\n const handleFindAnswer = async () => {\n setLoading(true);\n try {\n const res = await apiService.askQuestion(\n sentinel,\n jid,\n question,\n token,\n 'ZSB Platform'\n );\n const answer = res.data.report[0];\n const nodeId = uuid();\n composeNodes({\n id: nodeId,\n text: question,\n answer,\n });\n composeEdges(nodeId, answer?.jid);\n getNodesAndEdgesFromQuestion(answer, answer?.jid);\n setLoading(false);\n } catch (err) {\n setLoading(false);\n throw typeof err === 'string' ? err : err.message;\n }\n };\n\n const composeNodes = useCallback(\n ({ id, text, answer }) => {\n setNodes(prevNodes =>\n prevNodes.concat({\n id,\n data: { label: text, answer },\n })\n );\n },\n [setNodes]\n );\n\n const composeEdges = useCallback(\n (source, target) => {\n setEdges(prevEdges =>\n prevEdges.concat({\n id: `element-${target}`,\n source,\n target,\n })\n );\n },\n [setEdges]\n );\n\n const getNodesAndEdgesFromQuestion = useCallback(\n (answer, sourceId) => {\n if (typeof answer === 'object') {\n composeNodes({\n id: answer?.context?.id || answer.jid,\n text: answer?.context?.text,\n answer,\n });\n if (answer.context.quick_reply) {\n if (answer.context.quick_reply.replies) {\n const replies = answer.context.quick_reply.replies;\n for (const reply of replies) {\n const tempId = uuid();\n composeNodes({\n id: tempId,\n text: reply.reply,\n answer: reply.answer,\n });\n\n // if answer has quick reply, build the edges\n composeEdges(sourceId, tempId);\n }\n }\n } else {\n // else, build the edge from the original question and answer\n composeEdges(sourceId, answer?.context?.id || answer.jid);\n }\n }\n },\n [composeEdges, composeNodes]\n );\n\n const getNodesAndEdgesFromAnswer = useCallback(\n (answer, sourceId) => {\n if (typeof answer === 'object') {\n composeNodes({\n id: answer?.context?.id || answer.jid,\n text: answer?.context?.text,\n answer,\n });\n composeEdges(sourceId, answer?.context?.id || answer.jid);\n if (answer.context.quick_reply) {\n if (answer.context.quick_reply.replies) {\n const replies = answer.context.quick_reply.replies;\n for (const reply of replies) {\n const tempId = uuid();\n composeNodes({\n id: tempId,\n text: reply.reply,\n answer: reply.answer,\n });\n composeEdges(answer?.context?.id || answer.jid, tempId);\n }\n }\n }\n }\n },\n [composeEdges, composeNodes]\n );\n\n const getAnswerFromNode = useCallback(\n async (nodeData, sourceId) => {\n const nodeJid = nodeData.answer.jid;\n if (nodeJid) {\n try {\n const res = await apiService.getAnswer(\n sentinel,\n nodeJid,\n token,\n 'ZSB Platform'\n );\n const answer = res.data.report[0];\n getNodesAndEdgesFromAnswer(answer, sourceId);\n setLoading(false);\n } catch (err) {\n setLoading(false);\n throw typeof err === 'string' ? err : err.message;\n }\n } else {\n setLoading(true);\n try {\n const { label: text } = nodeData;\n const res = await apiService.askQuestion(\n sentinel,\n jid,\n text,\n token,\n 'ZSB Platform'\n );\n const answer = res.data.report[0];\n getNodesAndEdgesFromQuestion(answer, sourceId);\n setLoading(false);\n } catch (err) {\n setLoading(false);\n throw typeof err === 'string' ? err : err.message;\n }\n }\n },\n [\n getNodesAndEdgesFromAnswer,\n getNodesAndEdgesFromQuestion,\n jid,\n sentinel,\n token,\n ]\n );\n\n const onNodeClick = (event, node) => {\n const { id, data } = node;\n const source = layoutedEdges.find(edge => edge.source === id);\n\n // if source already exist, don't fetch data\n if (!source) {\n getAnswerFromNode(data, id);\n }\n };\n\n return {\n loading,\n question,\n layoutedNodes,\n layoutedEdges,\n handleFindAnswer,\n setQuestion,\n onNodesChange,\n onEdgesChange,\n onNodeClick,\n };\n};\n\nexport default useConversationSimulator;\n","import React from 'react';\nimport ReactFlow, { MiniMap, Controls, Background } from 'react-flow-renderer';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { Spin } from 'antd';\nimport Title from 'components/Title';\nimport useConversationSimulator from './hooks';\nimport TextArea from 'components/TextArea';\nimport Button from 'components/Button';\nimport { FlexRowSpaceBetweenWrapper } from 'components/Modals/AnswerEditor/Variant/Variant.styles';\n\nconst ErrorPage = () => (\n \n
Cannot load the page. Please refresh your browser. \n \n);\n\nconst ConversationSimulator = props => {\n const {\n loading,\n question,\n layoutedNodes,\n layoutedEdges,\n handleFindAnswer,\n setQuestion,\n onNodesChange,\n onEdgesChange,\n onNodeClick,\n } = useConversationSimulator();\n\n return (\n \n \n \n\n \n \n \n \n \n \n \n \n
\n \n \n );\n};\n\nConversationSimulator.propTypes = {};\n\nexport default ConversationSimulator;\n","import styled from 'styled-components';\n\nexport const StyledFileEditor = styled.div`\n display: flex;\n flex-direction: column;\n min-width: 100%;\n min-height: 20vh;\n margin-top: 4%;\n\n @media (max-width: 600px) {\n min-width: 80%;\n }\n\n .ant-descriptions-title {\n display: flex;\n align-items: flex-end;\n right: 0;\n flex-direction: column;\n }\n`;\n","import { useState, useEffect, useContext } from 'react';\nimport { message } from 'antd';\n\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport {\n CLOSE_FILE_EDITOR,\n UPDATE_FILE,\n UPDATE_FILE_EDITOR_FILE,\n} from 'store/action';\nimport { GET_DATA_ERROR } from 'constants/error';\nimport {\n isFileEditorOpenSelector,\n fileEditorAnswerSelector,\n} from 'selectors/bot/answers';\nimport useSelector from 'store/useSelector';\n\nconst useFileEditor = () => {\n const [state, dispatch] = useContext(Context);\n const fileEditorAnswer = useSelector(fileEditorAnswerSelector);\n const isOpen = useSelector(isFileEditorOpenSelector);\n const { sentinel, token } = state;\n\n const [previewDocs, setPreviewDocs] = useState([{ uri: null }]);\n const [confirmLoading, setConfirmLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n const [error, setError] = useState(null);\n const [isFullScreen, setFullScreen] = useState(false);\n const [showPreview, setShowPreview] = useState(false);\n\n useEffect(() => {\n if (showPreview && !previewDocs[0].uri) {\n generateFileUrl();\n }\n }, [previewDocs, showPreview]);\n\n const scrollIntoWarningView = () => {\n setTimeout(() => {\n const target = document.getElementsByClassName('ant-alert');\n if (target && target[0]) {\n target[0].scrollIntoViewIfNeeded();\n }\n }, 500);\n };\n\n const handleOnUpdateAnswer = async () => {\n setConfirmLoading(true);\n try {\n const res = await apiService.changeFile(\n sentinel,\n fileEditorAnswer?.id,\n token,\n fileEditorAnswer\n );\n\n if (res.data.success) {\n dispatch({\n type: UPDATE_FILE,\n payload: res.data.report[0],\n });\n setConfirmLoading(false);\n handleClose();\n } else {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n } catch (error) {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n setConfirmLoading(false);\n };\n\n const handleClearFileEditorLocalState = () => {\n setFullScreen(false);\n setError(null);\n setConfirmLoading(false);\n };\n\n const handleClose = () => {\n if (isFullScreen) {\n return setFullScreen(false);\n }\n handleClearFileEditorLocalState();\n dispatch({\n type: CLOSE_FILE_EDITOR,\n });\n };\n\n const handleChangeSwitch = async e => {\n try {\n dispatch({\n type: UPDATE_FILE_EDITOR_FILE,\n payload: {\n version: e ? 'final' : 'draft',\n },\n });\n } catch (error) {\n message.error(error.message || GET_DATA_ERROR);\n }\n };\n\n const generateFileUrl = async () => {\n try {\n const res = await apiService.generateFileCdnUrl(\n sentinel,\n fileEditorAnswer.id,\n token\n );\n const cdnUrl = res?.data?.report[0];\n setPreviewDocs([\n {\n uri: cdnUrl,\n },\n ]);\n } catch (error) {\n message.error('Error fetching file preview');\n }\n };\n\n const handleDownloadFile = async () => {\n setDownloadLoading(true);\n try {\n const res = await apiService.generateFileCdnUrl(\n sentinel,\n fileEditorAnswer.id,\n token\n );\n const cdnUrl = res.data.report[0];\n fetch(cdnUrl)\n .then(response => response.blob())\n .then(blob => {\n const link = document.createElement('a');\n const url = URL.createObjectURL(blob);\n link.href = url;\n link.download = fileEditorAnswer.file_name;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n setDownloadLoading(false);\n })\n .catch(error => console.error('Error fetching file:', error));\n } catch (err) {\n setDownloadLoading(false);\n throw typeof err === 'string' ? err : err.message;\n }\n };\n\n return {\n confirmLoading,\n handleClose,\n isFullScreen,\n handleOnUpdateAnswer,\n setFullScreen,\n handleChangeSwitch,\n isOpen,\n fileEditorAnswer,\n handleDownloadFile,\n downloadLoading,\n previewDocs,\n showPreview,\n setShowPreview,\n };\n};\n\nexport default useFileEditor;\n","import React, { useCallback } from 'react';\nimport { Tag, Switch, Descriptions } from 'antd';\nimport DocViewer, { DocViewerRenderers } from 'react-doc-viewer';\nimport {\n CheckCircleOutlined,\n DownloadOutlined,\n EditOutlined,\n EyeInvisibleTwoTone,\n EyeTwoTone,\n FileTextOutlined,\n SaveOutlined,\n} from '@ant-design/icons';\nimport { StyledFileEditor } from './FileEditor.styles';\nimport Modal from 'components/Modals/GenericModal';\nimport Button from 'components/Button';\nimport { DEFAULT_ANSWER_VERSION } from 'constants/answerbank/defaults';\nimport useFileEditor from './hooks';\n\nimport { cssVariables } from 'styles/root';\nimport ToggleFullScreenButton from 'components/Button/ToggleFullScreenButton';\nimport { StyledFlexLeftColumn } from 'styles/GenericStyledComponents';\n\nconst FileEditor = () => {\n const {\n confirmLoading,\n handleClose,\n isFullScreen,\n handleOnUpdateAnswer,\n setFullScreen,\n handleChangeSwitch,\n fileEditorAnswer,\n isOpen,\n handleDownloadFile,\n downloadLoading,\n previewDocs,\n showPreview,\n setShowPreview,\n } = useFileEditor();\n const isFinalAnswer =\n fileEditorAnswer?.version === 'final'\n ? true\n : false || fileEditorAnswer?.version === DEFAULT_ANSWER_VERSION;\n\n const renderModalTitle = () => (\n <>\n \n {'Edit File Version'}\n \n {isFinalAnswer ? 'Final' : 'Draft'}{' '}\n {isFinalAnswer ? : }\n \n \n \n >\n );\n\n const renderFilePreview = useCallback(() => {\n if (showPreview) {\n return (\n \n : }\n onClick={() => setShowPreview(!showPreview)}\n variant=\"link\"\n value={showPreview ? 'Hide Preview' : 'View'}\n bordered\n />\n \n \n );\n }\n return (\n : }\n onClick={() => setShowPreview(!showPreview)}\n variant=\"link\"\n value={showPreview ? 'Hide Preview' : 'View'}\n bordered\n />\n );\n }, [fileEditorAnswer, previewDocs, showPreview]);\n\n return (\n \n {'OK'} \n >\n }\n footer={\n \n \n : }\n onClick={handleOnUpdateAnswer}\n />\n
\n }\n >\n \n }\n variant=\"link\"\n loading={downloadLoading}\n />\n }\n layout=\"horizontal\"\n bordered\n span={1}\n >\n \n {fileEditorAnswer.file_name}\n \n \n \n \n \n {fileEditorAnswer.lastEdited}\n \n \n {renderFilePreview()}\n \n \n \n \n );\n};\n\nexport default FileEditor;\n","import { useEffect, useContext, useState } from 'react';\nimport { message } from 'antd';\n\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\nimport { botJIDSelector, isPageReadySelector } from 'selectors/bot';\nimport { aiTools } from 'services/aiTools.service';\nimport {\n CLOSE_AI_TOOL_MODAL,\n DELETE_AI_TOOL,\n OPEN_AI_TOOL_MODAL,\n SET_AI_TOOLS,\n SET_SELECTED_AI_TOOL,\n} from 'store/action';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport { aiToolsSelector } from 'selectors/bot/aiTools';\nimport { aiToolModalSelector } from 'selectors/bot/ui';\n\nconst INITIAL_SELECTED_TOOL_STATE = {\n jid: null,\n name: null,\n toDelete: false,\n loading: false,\n};\n\nconst useFunctions = () => {\n const [state, dispatch] = useContext(Context);\n const isPageReady = useSelector(isPageReadySelector);\n const botJID = useSelector(botJIDSelector);\n const allAITools = useSelector(aiToolsSelector);\n const toolModal = useSelector(aiToolModalSelector);\n const isToolModalOpen = toolModal.isOpen;\n\n const { sentinel } = state;\n\n const [selectedTool, setSelectedTool] = useState(INITIAL_SELECTED_TOOL_STATE);\n\n const getTools = async () => {\n try {\n const res = await aiTools.getTools(botJID);\n dispatch({\n type: SET_AI_TOOLS,\n payload: res.data.report,\n });\n } catch (error) {\n message.error(GET_DATA_ERROR);\n }\n };\n\n const handleViewTool = tool => {\n dispatch({\n type: SET_SELECTED_AI_TOOL,\n payload: tool,\n });\n };\n\n const handleAddTool = () => {\n dispatch({\n type: OPEN_AI_TOOL_MODAL,\n payload: {\n action: 'add',\n },\n });\n };\n\n const handleDeleteTool = async () => {\n setSelectedTool({ ...selectedTool, loading: true });\n try {\n await aiTools.deleteTool(selectedTool.jid);\n dispatch({\n type: DELETE_AI_TOOL,\n payload: selectedTool.jid,\n });\n handleCloseDeleteModal();\n } catch (error) {\n message.error(DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleCloseToolModal = () => {\n dispatch({\n type: CLOSE_AI_TOOL_MODAL,\n });\n };\n\n const handleCloseDeleteModal = () => {\n setSelectedTool(INITIAL_SELECTED_TOOL_STATE);\n };\n\n const handleClickDelete = ({ name, jid }) => {\n setSelectedTool({\n name,\n jid,\n toDelete: true,\n });\n };\n\n useEffect(() => {\n if (sentinel && isPageReady) {\n getTools();\n }\n }, [sentinel, isPageReady]);\n\n return {\n allAITools,\n isPageReady,\n isToolModalOpen,\n selectedTool,\n handleClickDelete,\n handleViewTool,\n handleAddTool,\n handleCloseToolModal,\n handleDeleteTool,\n handleCloseDeleteModal,\n };\n};\n\nexport default useFunctions;\n","import { Table, Form } from 'antd';\nimport Input from 'components/Input';\nimport styled from 'styled-components';\nimport {\n StyledFlexLeftColumn,\n StyledFlexRowLeft,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledToolsContent = styled(StyledFlexLeftColumn)`\n input,\n textarea {\n width: 100%;\n }\n\n .ant-btn {\n margin-top: 10px;\n }\n`;\n\nexport const StyledFormToolsContent = styled(Form)`\n width: ${props => (props.fullWidth ? '100% !important' : '60% !important')};\n input,\n textarea {\n width: 100%;\n }\n\n .ant-btn {\n margin-top: 10px;\n }\n`;\n\nexport const StyledFormItem = styled(Form.Item)`\n width: 100% !important;\n margin-bottom: 0 !important;\n > * {\n width: 100% !important;\n }\n\n > * + * {\n margin: 5px 0;\n }\n\n > label {\n width: 100px;\n }\n input,\n textarea {\n width: 100%;\n }\n\n .ant-input-sm {\n padding: 5px;\n width: 100% !important;\n }\n\n .anticon {\n height: max-content;\n width: auto;\n &:not(.anticon-delete) {\n color: ${cssVariables.primaryBlue};\n }\n\n &.anticon-delete {\n color: ${cssVariables.red10};\n }\n\n &.anticon-minus-circle {\n margin-left: 5px;\n color: ${cssVariables.red10};\n }\n }\n\n .ant-btn {\n margin-top: 10px;\n }\n`;\n\nexport const StyledFormRow = styled(StyledFlexLeftColumn)`\n width: 100% !important;\n > * {\n width: 100% !important;\n }\n\n > * + * {\n margin: 5px 0;\n }\n\n > label {\n width: 100px;\n }\n input,\n textarea {\n width: 100%;\n }\n\n .ant-input-sm {\n padding: 5px;\n width: 100% !important;\n }\n\n .anticon {\n height: max-content;\n width: auto;\n &:not(.anticon-delete) {\n color: ${cssVariables.primaryBlue};\n }\n\n &.anticon-delete {\n color: ${cssVariables.red10};\n }\n\n &.anticon-minus-circle {\n margin-left: 5px;\n color: ${cssVariables.red10};\n }\n }\n\n .ant-btn {\n margin-top: 10px;\n }\n`;\n\nexport const StyledNameInput = styled(Input)`\n width: 60% !important;\n margin-right: 5px;\n .ant-input {\n width: 100%;\n }\n`;\n\nexport const StyledValueInput = styled(Input)`\n width: 100% !important;\n .ant-input {\n width: 100%;\n }\n`;\n\nexport const StyledFieldRows = styled(StyledSpaceBetweenFlexCenter)`\n width: 100% !important;\n > * + * {\n margin: 0px 5px;\n }\n > * {\n width: 100%;\n }\n\n .ant-btn {\n margin-top: 10px;\n }\n`;\n\nexport const StyledDefaultPropName = styled(StyledFlexRowLeft)`\n background-color: ${cssVariables.gray3};\n border: 1px solid ${cssVariables.gray2};\n padding: 5px;\n border-radius: 5px;\n color: ${cssVariables.gray0};\n`;\n\nexport const StyledFunctionsTable = styled(Table)`\n .ant-table-tbody {\n .ant-table-row {\n .anticon {\n width: 40px;\n height: 40px;\n cursor: pointer;\n justify-content: center;\n\n &:hover {\n background-color: ${cssVariables.gray2};\n border-radius: 50%;\n }\n }\n }\n }\n`;\n\nexport const StyledBtnAdvanceSettings = styled.button`\n display: flex;\n border: none;\n background: #ffff;\n color: #1667e7;\n border-radius: 1px;\n cursor: pointer;\n line-height: 32px;\n width: auto;\n\n &:hover {\n background: #1667e726;\n }\n`;\n","import React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { DeleteFilled, MinusOutlined, PlusOutlined } from '@ant-design/icons';\nimport { isEmpty } from 'lodash';\n\nimport {\n StyledFieldRows,\n StyledFormRow,\n StyledNameInput,\n StyledValueInput,\n} from './StyledComponents';\nimport { cssVariables } from 'styles/root';\nimport {\n StyledFlexLeftColumn,\n StyledFlexRowLeft,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\n\nimport Button from 'components/Button';\nimport Select from 'components/Select';\nimport { DATA_TYPES, NEW_OBJECT_ITEM } from 'constants/aiTools';\nimport Input from 'components/Input';\nimport HRWithCenteredText from 'components/HR/HRWithCenteredText';\nimport { Collapse } from 'antd';\n\nconst { Panel } = Collapse;\nconst Parameters = props => {\n const { paramProps, setParamsProps, ...rest } = props;\n\n const handleChangeParamName = (e, prop) => {\n const modifiedProp = paramProps.map(item => {\n if (prop.position === item.position) {\n return {\n ...item,\n name: e.target.value,\n };\n }\n\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n const handleChangeParamDescription = (e, prop) => {\n const modifiedProp = paramProps.map((item, idx) => {\n if (prop.position === item.position) {\n return {\n ...item,\n description: e.target.value,\n };\n }\n\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n const onSelectParamPropType = (e, prop) => {\n const modifiedProp = paramProps.map((item, idx) => {\n if (prop.position === item.position) {\n if (e === 'object') {\n return {\n ...item,\n type: e,\n properties: !isEmpty(item.properties)\n ? item.properties\n : NEW_OBJECT_ITEM,\n };\n } else {\n return {\n ...item,\n type: e,\n properties: undefined,\n };\n }\n }\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n const deleteProps = position => {\n const filteredParams = paramProps.filter(\n prop => prop.position !== position\n );\n setParamsProps(filteredParams);\n };\n\n const handleChangeObjectFieldChange = (e, prop, field, position) => {\n const modifiedProp = paramProps.map(item => {\n if (prop.position === item.position) {\n return {\n ...item,\n properties: item.properties?.map((f, fieldIdx) => {\n if (fieldIdx === position) {\n return {\n ...f,\n [field]: field === 'type' ? e : e.target.value,\n };\n }\n return f;\n }),\n };\n }\n\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n const handleAddObjectField = prop => {\n const modifiedProp = paramProps.map(item => {\n if (prop.position === item.position && prop.type === 'object') {\n return {\n ...item,\n properties: [\n ...item.properties,\n { position: item.properties.length + 1, name: '' },\n ],\n };\n }\n\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n const handleDeleteObjectField = (prop, position) => {\n const modifiedProp = paramProps.map(item => {\n if (prop.position === item.position && prop.type === 'object') {\n return {\n ...item,\n properties: item.properties.filter(f => f.position !== position),\n };\n }\n\n return item;\n });\n setParamsProps(modifiedProp);\n };\n\n return (\n \n {paramProps.map((prop, idx) => {\n return (\n {prop.name || `property ${idx + 1}`}}\n extra={\n idx ? (\n }\n onClick={() => deleteProps(prop.position, idx)}\n />\n ) : (\n }\n variant=\"link\"\n size=\"small\"\n onClick={props.addProps}\n />\n )\n }\n key={idx}\n >\n \n \n \n onSelectParamPropType(e, prop)}\n defaultValue={prop.type}\n />\n {prop.type === 'object' ? (\n handleChangeParamName(e, prop)}\n />\n ) : null}\n {prop.type !== 'object' ? (\n \n handleChangeParamName(e, prop)}\n />\n handleChangeParamDescription(e, prop)}\n />\n \n ) : null}\n {prop.type === 'object' ? (\n \n {`${\n prop.name || `property ${idx + 1}`\n } Properties:`} \n {(prop.properties || []).map((f, fieldIndex) => {\n return (\n \n \n {prop.type === 'object' ? (\n i.value !== 'object'\n )}\n onChange={e =>\n handleChangeObjectFieldChange(\n e,\n prop,\n 'type',\n fieldIndex\n )\n }\n />\n ) : null}\n \n \n handleChangeObjectFieldChange(\n e,\n prop,\n 'name',\n fieldIndex\n )\n }\n />\n \n handleChangeObjectFieldChange(\n e,\n prop,\n 'description',\n fieldIndex\n )\n }\n />\n {fieldIndex ? (\n \n handleDeleteObjectField(prop, f.position)\n }\n disabled={!fieldIndex}\n />\n ) : (\n \n )}\n \n \n );\n })}\n {prop.type === 'object' ? (\n \n handleAddObjectField(prop)}\n value=\"Add field\"\n />\n \n ) : null}\n \n ) : null}\n \n \n \n \n );\n })}\n \n );\n};\n\nParameters.propTypes = {\n paramProps: PropTypes.array.isRequired,\n setParamsProps: PropTypes.func.isRequired,\n objectifiedParamProps: PropTypes.func.isRequired,\n addProps: PropTypes.func.isRequired,\n};\n\nexport default Parameters;\n","import React, { useMemo } from 'react';\nimport PropTypes from 'prop-types';\nimport { Collapse, Typography } from 'antd';\nimport TextArea from 'components/TextArea';\nimport Input from 'components/Input';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\nimport Select from 'components/Select';\nimport Button from 'components/Button';\nimport {\n StyledFormItem,\n StyledFormRow,\n StyledFormToolsContent,\n} from './StyledComponents';\nimport { orderBy } from 'lodash';\nimport {\n DeleteOutlined,\n InfoCircleTwoTone,\n PlusOutlined,\n} from '@ant-design/icons';\nimport { AUTH_OPTIONS } from 'constants/aiTools';\nimport { keyPropertyValidator } from 'store/reducers/helpers/bot/aiTools';\nimport {\n StyledFlexRowRight,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\n\nconst { Text } = Typography;\nconst { Panel } = Collapse;\n\nconst Auth = props => {\n const { outputProps, setOutputProps, ...rest } = props;\n const orderAuth = value => orderBy(value, 'position', 'asc');\n const authProps = useMemo(\n () => outputProps.find(output => output.name === 'auth')?.request,\n [outputProps]\n );\n\n const displayField = authDetails => {\n const fieldConnected = authProps?.find(\n auth => auth.name === authDetails.display?.relatedTo && auth.value\n );\n const authType = authDetails.authType === authProps[0]?.value;\n\n if (!authType && authDetails.name !== 'type') {\n return false;\n }\n\n return !authDetails?.display?.onload && !fieldConnected ? false : true;\n };\n\n const getAuthPropertyDetails = value => {\n return authProps?.find(auth => auth.name === value);\n };\n\n const handleFieldChanges = (value, name) => {\n if (name === 'type') {\n setOutputProps(\n orderAuth([\n ...outputProps.filter(output => output.name !== 'auth'),\n {\n ...outputProps.find(output => output.name === 'auth'),\n type: value,\n request: orderAuth([\n ...authProps.filter(auth => auth.name !== name),\n {\n ...authProps.find(auth => auth.name === name),\n value,\n },\n ]),\n },\n ])\n );\n } else {\n setOutputProps(\n orderAuth([\n ...outputProps.filter(output => output.name !== 'auth'),\n {\n ...outputProps.find(output => output.name === 'auth'),\n request: orderAuth([\n ...authProps.filter(auth => auth.name !== name),\n {\n ...authProps.find(auth => auth.name === name),\n value,\n },\n ]),\n },\n ])\n );\n }\n };\n\n const handleChangeProperties = (name, value, field, propertyInfo, idx) => {\n const authSelected = authProps?.find(auth => auth.name === name);\n const propertyExist = authSelected?.properties?.find(\n auth => auth.id === idx\n );\n const currentProperties = authSelected?.properties?.filter(\n auth => auth.id !== idx\n );\n\n const propertyValue = propertyExist ? propertyExist?.value : propertyInfo;\n const propertyKey = propertyExist ? propertyExist?.name : propertyInfo;\n const propertyId = propertyExist ? idx : currentProperties?.length;\n const properties = orderBy(\n [\n ...currentProperties,\n {\n id: propertyId,\n name: field === 'key' ? value : propertyKey,\n value: field === 'key' ? propertyValue : value,\n validator: name === 'query' ? keyPropertyValidator : null,\n },\n ],\n 'id',\n 'asc'\n );\n\n setOutputProps(\n orderAuth([\n ...outputProps.filter(output => output.name !== 'auth'),\n {\n ...outputProps.find(output => output.name === 'auth'),\n request: orderAuth([\n ...authProps?.filter(auth => auth.name !== name),\n {\n ...authSelected,\n properties,\n },\n ]),\n },\n ])\n );\n };\n\n const addAuthProperties = name => {\n const filterField = authProps?.find(auth => auth.name === name);\n const properties = filterField?.properties\n ? [\n ...filterField?.properties,\n { id: filterField?.properties?.length, name: '', value: null },\n ]\n : [{ id: 0, name: '', value: null }];\n\n setOutputProps(\n orderAuth([\n ...outputProps.filter(output => output.name !== 'auth'),\n {\n ...outputProps.find(output => output.name === 'auth'),\n request: orderAuth([\n ...authProps?.filter(auth => auth.name !== name),\n {\n ...filterField,\n properties,\n },\n ]),\n },\n ])\n );\n };\n\n const deleteAuthProperty = (propertyName, idToRemove) => {\n const authSelected = authProps?.find(auth => auth.name === propertyName);\n const newProperty = authSelected.properties\n ?.filter(obj => obj.id !== idToRemove)\n .map((obj, id) => ({ ...obj, id }));\n\n setOutputProps(\n orderAuth([\n ...outputProps.filter(output => output.name !== 'auth'),\n {\n ...outputProps.find(output => output.name === 'auth'),\n request: orderAuth([\n ...authProps?.filter(auth => auth.name !== propertyName),\n {\n ...authSelected,\n properties: orderBy(newProperty, 'id', 'asc'),\n },\n ]),\n },\n ])\n );\n };\n\n const RenderCollapsePanel = () => (\n \n {authProps &&\n authProps\n ?.filter(auth => auth.collapsibleField)\n ?.map(\n auth =>\n displayField(auth) && (\n {auth.label}}\n >\n \n {auth?.action?.find(action => action === 'add') && (\n }\n variant=\"link\"\n size=\"small\"\n onClick={() => addAuthProperties(auth.name)}\n />\n )}\n \n {auth?.properties &&\n auth?.properties?.map((property, idx) => (\n \n \n Key: \n \n handleChangeProperties(\n auth.name,\n evt.target.value,\n 'key',\n property.value,\n property.id\n )\n }\n style={{ marginRight: '5px' }}\n />\n \n \n Value: \n \n handleChangeProperties(\n auth.name,\n evt.target.value,\n 'value',\n property.name,\n property.id\n )\n }\n disabled={!property.name}\n style={{ marginLeft: '5px' }}\n />\n \n {auth?.action?.find(action => action === 'delete') &&\n idx ? (\n }\n variant=\"link\"\n onClick={() =>\n deleteAuthProperty(auth.name, property.id)\n }\n />\n ) : null}\n \n ))}\n \n )\n )}\n \n );\n\n const RenderInputField = auth => (\n \n \n {auth.label} \n {auth.tooltip && (\n \n \n \n )}\n >\n }\n value={getAuthPropertyDetails(auth.name)?.value}\n defaultValue={getAuthPropertyDetails(auth.name)?.value}\n onChange={evt => handleFieldChanges(evt.target.value, auth.name)}\n placeholder={auth.placeholder}\n />\n \n );\n\n const RenderTextAreaField = auth => (\n \n \n );\n\n const RenderSelectField = auth => (\n \n \n {auth.label} {' '}\n {auth.tooltip && (\n \n \n \n )}\n >\n }\n options={auth.options}\n onChange={evt => handleFieldChanges(evt, auth.name)}\n value={auth.value}\n defaultValue={auth.value}\n />\n \n );\n\n return (\n \n {authProps &&\n authProps\n ?.filter(auth => !auth.collapsibleField)\n .map((auth, propIdx) => (\n \n {auth.field === 'input' &&\n displayField(auth) &&\n RenderInputField(auth)}\n {auth.field === 'textArea' &&\n displayField(auth) &&\n RenderTextAreaField(auth)}\n {auth.field === 'select' &&\n displayField(auth) &&\n RenderSelectField(auth)}\n
\n ))}\n {RenderCollapsePanel()}\n \n );\n};\n\nAuth.propTypes = {\n outputProps: PropTypes.array.isRequired,\n setOutputProps: PropTypes.func.isRequired,\n};\nexport default Auth;\n","import React, { useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport {\n InfoCircleTwoTone,\n PlusOutlined,\n DeleteOutlined,\n} from '@ant-design/icons';\nimport { orderBy } from 'lodash';\n\nimport { StyledFormItem, StyledFormRow } from './StyledComponents';\nimport {\n StyledFlexRowRight,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\n\nimport Button from 'components/Button';\nimport Select from 'components/Select';\nimport { RESPONSE_OPTIONS, SELECTABLE_FIELDS } from 'constants/aiTools';\nimport { Typography, Tabs } from 'antd';\nimport Input from 'components/Input';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\nimport TextArea from 'components/TextArea';\nimport { keyPropertyValidator } from 'store/reducers/helpers/bot/aiTools';\nimport TabPane from 'antd/lib/tabs/TabPane';\nimport Parameters from './Parameters';\nimport Auth from './Auth';\n\nconst { Text } = Typography;\nconst Output = props => {\n const {\n outputProps,\n requiredFields,\n getOutputPropertyDetails,\n setOutputProps,\n advanceSettingOption,\n showAdvancedSettings,\n setParamsProps,\n paramProps,\n objectifiedParamProps,\n addProps,\n setRequiredFields,\n ...rest\n } = props;\n const orderOutput = value => orderBy(value, 'position', 'asc');\n const [activeTab, setActiveTab] = useState('parameters');\n const isUrlValid = useMemo(() => {\n try {\n new URL(outputProps?.find(output => output.name === 'url')?.value);\n return true;\n } catch (error) {\n return false;\n }\n }, outputProps);\n\n const displayField = outputDetails => {\n const fieldConnected = outputProps.find(\n output => output.name === outputDetails.display?.relatedTo && output.value\n );\n\n return !outputDetails?.display?.onload && !fieldConnected ? false : true;\n };\n\n const deleteProperty = (propertyName, idToRemove) => {\n const outputSelected = outputProps?.find(\n output => output.name === propertyName\n );\n const newProperty = outputSelected.properties\n ?.filter(obj => obj.id !== idToRemove)\n .map((obj, id) => ({ ...obj, id }));\n\n const newOutput = [\n ...outputProps?.filter(output => output.name !== propertyName),\n {\n ...outputSelected,\n properties: orderBy(newProperty, 'id', 'asc'),\n },\n ];\n\n setOutputProps(orderOutput(newOutput));\n };\n\n const handleChangeProperties = (name, value, field, propertyInfo, idx) => {\n const outputSelected = outputProps?.find(output => output.name === name);\n const propertyExist = outputSelected?.properties?.find(\n output => output.id === idx\n );\n const currentProperties = outputSelected?.properties?.filter(\n output => output.id !== idx\n );\n\n const propertyValue = propertyExist ? propertyExist?.value : propertyInfo;\n const propertyKey = propertyExist ? propertyExist?.name : propertyInfo;\n const propertyId = propertyExist ? idx : currentProperties?.length;\n const properties = orderBy(\n [\n ...currentProperties,\n {\n id: propertyId,\n name: field === 'key' ? value : propertyKey,\n value: field === 'key' ? propertyValue : value,\n validator: name === 'query' ? keyPropertyValidator : null,\n },\n ],\n 'id',\n 'asc'\n );\n\n if (name === 'path') {\n const urlProperty = outputProps?.find(output => output.name === 'url');\n const newUrl = urlProperty.pathValue?.replace(`{${propertyKey}}`, value);\n\n const renderedOutput = [\n ...outputProps?.filter(\n output => output.name !== name && output.name !== 'url'\n ),\n {\n ...urlProperty,\n value: newUrl,\n },\n {\n ...outputSelected,\n properties,\n },\n ];\n\n setOutputProps(orderOutput(renderedOutput));\n } else {\n const renderedOutput = [\n ...outputProps?.filter(output => output.name !== name),\n {\n ...outputSelected,\n properties,\n },\n ];\n\n setOutputProps(orderOutput(renderedOutput));\n }\n };\n\n const handleOutputPropsChanges = (value, name) => {\n setOutputProps(currentOutputProps => {\n const selectedOutput = currentOutputProps?.find?.(\n output => output.name === name\n );\n\n if (selectedOutput) {\n const selectedOutputProperties = {\n ...selectedOutput,\n name,\n value,\n pathValue: value,\n properties: !showProperties(value) ? [] : selectedOutput?.properties,\n };\n\n const isManualPathVariable = currentOutputProps.find(\n output => output.name === 'path' && output.value === 'manual'\n );\n const isOpenAiPathVariable = currentOutputProps.find(\n output => output.name === 'path' && output.value === '{{$}}'\n );\n\n if (\n (name === 'url' && isManualPathVariable) ||\n (name === 'path' && value === 'manual')\n ) {\n const matches =\n name === 'url'\n ? value?.match(/\\{([^}]+)\\}/g)\n : currentOutputProps\n ?.find(output => output.name === 'url')\n .value.match(/\\{([^}]+)\\}/g);\n\n const newPathVariableProperties = matches\n ? matches?.reduce((acc, property) => {\n return [\n ...acc,\n {\n id: acc.length,\n name: property.substring(1, property.length - 1),\n value: '',\n },\n ];\n }, [])\n : [{ id: 0, name: '', value: null }];\n\n const newProperties = {\n ...currentOutputProps?.find(output => output.name === 'path'),\n value: 'manual',\n properties: newPathVariableProperties,\n };\n\n const outputs =\n name === 'url'\n ? [\n ...currentOutputProps?.filter?.(\n output => !['url', 'path'].includes(output.name)\n ),\n newProperties,\n selectedOutputProperties,\n ]\n : [\n ...currentOutputProps?.filter?.(\n output => output.name !== 'path'\n ),\n newProperties,\n ];\n\n return orderOutput(outputs);\n } else if (\n (name === 'url' && isOpenAiPathVariable) ||\n (name === 'path' && value === '{{$}}')\n ) {\n return renderPathVariableOpenAiUrl(\n currentOutputProps,\n name,\n selectedOutputProperties,\n value\n );\n } else {\n return orderOutput([\n ...currentOutputProps?.filter?.(output => output.name !== name),\n selectedOutputProperties,\n ]);\n }\n } else {\n return orderOutput(currentOutputProps);\n }\n });\n };\n\n const addOutputProperties = name => {\n const filterField = outputProps?.find(output => output.name === name);\n const properties = filterField?.properties\n ? [\n ...filterField?.properties,\n { id: filterField?.properties?.length, name: '', value: '' },\n ]\n : [{ id: 0, name: '', value: '' }];\n\n const output = [\n ...outputProps?.filter(output => output.name !== name),\n {\n ...filterField,\n properties,\n },\n ];\n setOutputProps(orderOutput(output));\n };\n\n const renderPathVariableOpenAiUrl = (\n currentOutputProps,\n name,\n selectedOutputProperies,\n value\n ) => {\n try {\n if (isUrlValid) {\n const selectedValue =\n name === 'url'\n ? value\n : currentOutputProps?.find(output => output.name === 'url').value;\n const url = new URL(selectedValue);\n const pathVariableValue =\n name === 'url'\n ? currentOutputProps?.find(output => output.name === 'path').value\n : value;\n\n const openAiURL =\n requiredFields && pathVariableValue === '{{$}}'\n ? requiredFields?.reduce((acc, requiredField) => {\n return `${acc}/{${requiredField}}`;\n }, url.origin)\n : selectedValue;\n\n if (name === 'url') {\n return orderOutput([\n ...currentOutputProps?.filter?.(output => output.name !== name),\n {\n ...selectedOutputProperies,\n value: openAiURL,\n pathValue: openAiURL,\n },\n ]);\n } else {\n return orderOutput([\n ...currentOutputProps?.filter?.(\n output => output.name !== name && output.name !== 'url'\n ),\n {\n ...currentOutputProps?.find(output => output.name === 'url'),\n value: openAiURL,\n pathValue: openAiURL,\n },\n selectedOutputProperies,\n ]);\n }\n }\n } catch (error) {\n console.log(error);\n }\n };\n\n const renderAdvanceSettingCondition = output => {\n return advanceSettingOption\n ? !output.advanceSettingOption ||\n (output.advanceSettingOption && !showAdvancedSettings)\n : output.advanceSettingOption;\n };\n\n const requiredFieldsOptions = useMemo(() => {\n return objectifiedParamProps\n ? Object.keys(objectifiedParamProps) || []\n : [];\n }, [objectifiedParamProps]);\n\n const handleRequiredFieldChanges = value => {\n setRequiredFields(value);\n const pathVariableProperty = getOutputPropertyDetails('path');\n if (pathVariableProperty.value === '{{$}}' && isUrlValid) {\n setOutputProps(currentOutput => {\n const urlOutput = currentOutput?.find?.(\n output => output.name === 'url'\n );\n const url = new URL(urlOutput.value);\n const openAiURL = requiredFields\n ? requiredFields?.reduce((acc, requiredField) => {\n return `${acc}/{${requiredField}}`;\n }, url.origin)\n : urlOutput.value;\n\n return orderOutput([\n ...currentOutput.filter(output => output.name !== 'url'),\n {\n ...urlOutput,\n value: openAiURL,\n },\n ]);\n });\n }\n };\n\n const showProperties = method => {\n return (\n RESPONSE_OPTIONS.find(methodTypes => methodTypes.value === method)\n ?.showProperties || false\n );\n };\n\n const TabAdvancedSettings = () => {\n return (\n showAdvancedSettings && (\n setActiveTab(key)}>\n Parameters} key=\"parameters\">\n \n\n {!!requiredFieldsOptions && (\n \n {' '}\n Required Fields \n handleRequiredFieldChanges(value)}\n value={requiredFields}\n />\n \n )}\n \n {outputProps?.map(output =>\n ['type', 'auth'].includes(output.name) ||\n output.hide ||\n !output.advanceSettingOption ||\n (!showAdvancedSettings &&\n !SELECTABLE_FIELDS.includes(output.name) &&\n !displayField(output)) ? null : (\n {output.label}}\n key={output.name}\n >\n \n {output.name === 'header' &&\n getOutputPropertyDetails(output.name)?.properties?.length ===\n 0 ? (\n }\n variant=\"link\"\n size=\"small\"\n onClick={() => addOutputProperties(output.name)}\n />\n ) : (\n showProperties(output?.value) &&\n output?.action?.find(action => action === 'add') && (\n }\n variant=\"link\"\n size=\"small\"\n onClick={() => addOutputProperties(output.name)}\n />\n )\n )}\n \n \n output.validator(\n _,\n value,\n output.name,\n requiredFields,\n outputProps\n ),\n }\n : {\n required: output.required || false,\n message: output.placeholder,\n },\n ]}\n >\n {output?.options && (\n <>\n {output?.optionLabel} {' '}\n \n handleOutputPropsChanges(evt, output.name)\n }\n value={output.value}\n />\n >\n )}\n \n {output?.options &&\n showProperties(output?.value) &&\n output?.properties?.map((property, idx) => (\n \n \n property.validator(\n _,\n value,\n output.name,\n requiredFields\n ),\n }\n : null,\n ]}\n >\n {console.log(property.name)}\n Key:}\n defaultValue={property.name}\n value={property.name}\n size=\"small\"\n placeholder={'Enter key...'}\n onChange={evt =>\n handleChangeProperties(\n output.name,\n evt.target.value,\n 'key',\n property.value,\n property.id\n )\n }\n style={{ marginRight: '5px' }}\n disabled={output.disableNameProperty}\n />\n \n \n {console.log(property.value)}\n \n Value:{' '}\n {output.name === 'path' &&\n output.value === 'manual' && (\n \n {' '}\n \n \n )}\n \n }\n defaultValue={property.value}\n value={property.value}\n size=\"small\"\n placeholder={'Enter value...'}\n onChange={evt =>\n handleChangeProperties(\n output.name,\n evt.target.value,\n 'value',\n property.name,\n property.id\n )\n }\n style={{ marginLeft: '5px' }}\n disabled={!property.name}\n />\n \n {output?.action?.find(action => action === 'delete') &&\n idx ? (\n }\n variant=\"link\"\n onClick={() =>\n deleteProperty(output.name, property.id)\n }\n />\n ) : null}\n \n ))}\n {!output?.options &&\n output?.properties?.map((property, idx) => (\n \n \n Key: \n \n handleChangeProperties(\n output.name,\n evt.target.value,\n 'key',\n property.value,\n property.id\n )\n }\n style={{ marginRight: '5px' }}\n />\n \n \n \n Value:\n {output.name === 'path' && output.value === 'manual' && (\n \n {' '}\n \n \n )}\n \n \n handleChangeProperties(\n output.name,\n evt.target.value,\n 'value',\n property.name,\n property.id\n )\n }\n disabled={!property.name}\n style={{ marginLeft: '5px' }}\n />\n \n {output?.action?.find(action => action === 'delete') &&\n idx ? (\n }\n variant=\"link\"\n onClick={() =>\n deleteProperty(output.name, property.id)\n }\n />\n ) : null}\n \n ))}\n \n )\n )}\n\n Auth} key=\"auth\">\n \n \n \n )\n );\n };\n\n const RenderInputField = output => (\n \n output.validator(\n _,\n value,\n output.name,\n requiredFields,\n outputProps\n ),\n }\n : {\n required: output.required || false,\n message: output.placeholder,\n },\n ]}\n >\n \n {output.label} \n {output.tooltip && (\n \n \n \n )}\n >\n }\n value={getOutputPropertyDetails(output.name)?.value}\n defaultValue={getOutputPropertyDetails(output.name)?.value}\n onChange={evt =>\n handleOutputPropsChanges(evt.target.value, output.name)\n }\n placeholder={output.placeholder}\n />\n \n );\n\n const RenderTextAreaField = output => (\n \n output.validator(_, value, output.name, requiredFields),\n }\n : {\n required: output.required || false,\n message: output.placeholder,\n },\n ]}\n >\n \n );\n\n return (\n \n {outputProps?.map((output, propIdx) =>\n output.name === 'type' ||\n output.hide ||\n renderAdvanceSettingCondition(output) ||\n SELECTABLE_FIELDS.includes(output.name) ? null : (\n
\n {output.field === 'input' &&\n displayField(output) &&\n RenderInputField(output)}\n {output.field === 'textArea' &&\n displayField(output) &&\n RenderTextAreaField(output)}\n {output.field === 'select' &&\n displayField(output) &&\n !SELECTABLE_FIELDS.includes(output.name) && (\n \n \n {output.label} {' '}\n {output.tooltip && (\n \n \n \n )}\n >\n }\n options={output.options}\n onChange={evt => handleOutputPropsChanges(evt, output.name)}\n value={output.value}\n defaultValue={output.value}\n />\n \n )}\n
\n )\n )}\n\n {showAdvancedSettings && TabAdvancedSettings()}\n
\n );\n};\n\nOutput.propTypes = {\n outputProps: PropTypes.array.isRequired,\n requiredFields: PropTypes.array.isRequired,\n getOutputPropertyDetails: PropTypes.func.isRequired,\n setOutputProps: PropTypes.func.isRequired,\n advanceSettingOption: PropTypes.bool.isRequired,\n showAdvancedSettings: PropTypes.bool.isRequired,\n setParamsProps: PropTypes.func,\n paramProps: PropTypes.array,\n objectifiedParamProps: PropTypes.any,\n addProps: PropTypes.func,\n setRequiredFields: PropTypes.func,\n};\n\nexport default Output;\n","import React, { useContext, useMemo, useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { isEmpty, isEqual } from 'lodash';\nimport { Spin, Switch, Tag, message, Typography, Radio } from 'antd';\n\nimport Modal from 'components/Modals/GenericModal';\nimport {\n StyledFlexRowLeft,\n StyledFlexRowRight,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\nimport Input from 'components/Input';\nimport TextArea from 'components/TextArea';\nimport { InfoCircleTwoTone } from '@ant-design/icons';\nimport {\n StyledFormToolsContent,\n StyledFormItem,\n StyledBtnAdvanceSettings,\n} from './StyledComponents';\nimport Button from 'components/Button';\nimport { aiTools } from 'services/aiTools.service';\nimport useSelector from 'store/useSelector';\nimport CodeBlock from 'components/CodeBlock';\nimport { botJIDSelector } from 'selectors/bot';\nimport {\n DEFAULT_STATIC_OUTPUT_FIElDS,\n DEFAULT_URL_OUTPUT_FIElDS,\n DEFAULT_WALKER_OUTPUT_FIElDS,\n NEW_OBJECT_ITEM,\n OUTPUT_TYPES,\n} from 'constants/aiTools';\nimport { aiToolModalSelector } from 'selectors/bot/ui';\nimport { Context } from 'store/store';\nimport { ADD_AI_TOOL, EDIT_AI_TOOL } from 'store/action';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\nimport Output from './Output';\nimport { isMobileViewSelector } from 'selectors/layout';\n\nconst { Text } = Typography;\n\nconst FunctionModal = props => {\n const [, dispatch] = useContext(Context);\n const botJID = useSelector(botJIDSelector);\n const isMobileView = useSelector(isMobileViewSelector);\n const selectedTool = useSelector(aiToolModalSelector);\n const modalAction = selectedTool.action;\n const { title, onClose } = props;\n const [paramProps, setParamsProps] = useState(\n selectedTool.parameters?.properties?.length > 0\n ? selectedTool.parameters?.properties\n : NEW_OBJECT_ITEM\n );\n const [outputProps, setOutputProps] = useState(\n selectedTool.output || DEFAULT_URL_OUTPUT_FIElDS\n );\n const [toolName, setToolName] = useState(selectedTool.name || null);\n const [toolDescription, setToolDescription] = useState(\n selectedTool.description || null\n );\n const [version, setVersion] = useState(selectedTool.version || 'draft');\n const [requiredFields, setRequiredFields] = useState(\n selectedTool?.parameters?.required || []\n );\n const [modalLoading, setModalLoading] = useState(false);\n const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);\n\n const isFormValid = useMemo(() => {\n const isOutputValid =\n outputProps?.filter(\n output => output.required === true && output.value === ''\n ).length === 0;\n\n return toolName && toolDescription && isOutputValid;\n }, [toolName, toolDescription, outputProps]);\n\n const addProps = (evt, isOutputProps) => {\n const newKey = isOutputProps ? outputProps.length : paramProps.length;\n const newProps = {\n position: newKey + 1,\n name: '',\n };\n\n return isOutputProps\n ? setOutputProps([...outputProps, newProps])\n : setParamsProps([...paramProps, newProps]);\n };\n\n const onSelectOutputType = e => {\n setShowAdvancedSettings(false);\n if (e.target.value === selectedTool?.outputInTable?.type) {\n // fill output props with previous values\n setOutputProps(selectedTool.output);\n } else {\n switch (e.target.value) {\n case 'url':\n setOutputProps(DEFAULT_URL_OUTPUT_FIElDS);\n break;\n\n case 'walker':\n setOutputProps(DEFAULT_WALKER_OUTPUT_FIElDS);\n break;\n\n default:\n setOutputProps(DEFAULT_STATIC_OUTPUT_FIElDS);\n break;\n }\n }\n };\n\n const handleChangeToolName = e => {\n setToolName(e.target.value);\n };\n\n const handleChangeToolDescription = e => {\n setToolDescription(e.target.value);\n };\n\n const handleSubmitTool = async e => {\n e.preventDefault();\n try {\n setModalLoading(true);\n if (modalAction === 'add') {\n const output = objectifiedOutput;\n\n if (!output.auth?.type) {\n delete output.auth;\n }\n\n const res = await aiTools.addTool(\n toolName,\n toolDescription,\n objectifiedParamProps?.hasOwnProperty() ? objectifiedParamProps : {},\n objectifiedOutput,\n version,\n requiredFields,\n botJID\n );\n\n dispatch({\n type: ADD_AI_TOOL,\n payload: res.data.report,\n });\n } else {\n const {\n name,\n description,\n parameters,\n jid: toolJid,\n output,\n version: currentVersion,\n } = selectedTool;\n const res = await aiTools.changeTool(toolJid, {\n // removing unchanged props\n name: toolName === name ? undefined : toolName,\n description:\n toolDescription === description ? undefined : toolDescription,\n ver: version === currentVersion ? undefined : version,\n requiredFields: isEqual(parameters.required, requiredFields)\n ? undefined\n : requiredFields,\n properties: isEqual(paramProps, parameters?.properties)\n ? undefined\n : objectifiedParamProps,\n output: isEqual(output, outputProps) ? undefined : objectifiedOutput,\n });\n\n dispatch({\n type: EDIT_AI_TOOL,\n payload: res.data.report,\n });\n }\n setShowAdvancedSettings(false);\n setModalLoading(false);\n message.success(\n `Successfully ${modalAction === 'add' ? 'saved' : 'updated'} AI tool`\n );\n } catch (error) {\n setShowAdvancedSettings(false);\n setModalLoading(false);\n message.error(error?.message || 'Error saving AI tool');\n }\n };\n\n const objectifiedParamProps = useMemo(() => {\n return paramProps?.reduce((acc, item) => {\n const key = item.name;\n const objectProperties = {};\n\n if (!isEmpty(item.properties)) {\n item.properties.forEach(item => {\n const { name, value, position, ...rest } = item;\n // dont include name, value and position\n objectProperties[name] = rest;\n });\n }\n const newItem = {\n ...item,\n properties: isEmpty(item.properties) ? undefined : objectProperties,\n };\n // delete fields not to be shown on preview object\n delete newItem.position;\n delete newItem.name;\n\n acc[key] = newItem;\n\n return acc;\n }, {});\n }, [paramProps]);\n\n const authParamsPropsOutput = auth => {\n const authTypeValue = auth?.request?.find(\n request => request.name === 'type'\n )?.value;\n const objectType = authTypeValue === 'bearer' ? 'header' : 'request';\n\n const objectRequest = auth?.request?.reduce(\n (acc, item) => {\n const key = item?.name || '';\n const value = item?.value || null;\n const objectProperties = {};\n const displayData = item.authType === authTypeValue;\n if (\n item.type === 'object' &&\n !isEmpty(item.properties) &&\n displayData\n ) {\n item.properties.forEach(item => {\n const { name, value } = item;\n // pair the name & value\n // to create key value pair as an object item\n // for the properties array\n if (name && name !== 'type') {\n objectProperties[name] = value && value !== 'null' ? value : null;\n }\n });\n // assign the object to create a nested object for 'object' type\n const newItem = {\n [key]: objectProperties,\n };\n\n return Object.assign(acc, newItem);\n } else if (displayData) {\n // assign directly the name & value\n // to create key value pair\n const newItem = {\n [key]: value && value !== 'null' ? value : null,\n };\n\n return Object.assign(acc, newItem);\n } else {\n return acc;\n }\n },\n // initialize the accumulator with the output type\n // other items are stored in an array\n // so we use `reduce` to create nested object from the array\n {}\n );\n\n return {\n [auth.name]: {\n type: authTypeValue,\n [objectType]: objectRequest,\n },\n };\n };\n\n const objectifiedOutput = useMemo(() => {\n return outputProps?.reduce(\n (acc, item) => {\n const key = item?.name || '';\n const value = item?.value || null;\n const objectProperties = {};\n if (item.type === 'object' && !isEmpty(item.properties)) {\n item.properties.forEach(item => {\n const { name, value } = item;\n // pair the name & value\n // to create key value pair as an object item\n // for the properties array\n if (name) {\n objectProperties[name] = value && value !== 'null' ? value : null;\n }\n });\n // assign the object to create a nested object for 'object' type\n const newItem = {\n [key]: objectProperties,\n };\n\n return Object.assign(acc, newItem);\n } else if (item.name === 'auth') {\n const newItem = authParamsPropsOutput(item);\n return Object.assign(acc, newItem);\n } else {\n // assign directly the name & value\n // to create key value pair\n const newItem = {\n [key]: value && value !== 'null' ? value : null,\n };\n\n return Object.assign(acc, newItem);\n }\n },\n // initialize the accumulator with the output type\n // other items are stored in an array\n // so we use `reduce` to create nested object from the array\n {}\n );\n }, [outputProps]);\n\n const selectedOutput = useMemo(() => {\n if (outputProps) {\n return outputProps[0]?.value;\n }\n }, [outputProps]);\n\n const getOutputPropertyDetails = value => {\n return outputProps?.find(output => output.name === value);\n };\n\n const validateToolName = (_, value) => {\n const regex = /^[a-zA-Z0-9_-]{1,64}$/;\n if (!value) {\n return Promise.reject('Please enter your tool name.');\n }\n\n if (!regex.test(value)) {\n return Promise.reject('Please enter a valid tool name.');\n }\n return Promise.resolve();\n };\n\n const CodeBlockDetails = () => (\n \n );\n\n return (\n \n \n {title ||\n `${selectedTool?.action === 'edit' ? 'Edit' : 'Add'} Tool`}\n \n \n \n {version.toUpperCase()}\n \n \n }\n onCancel={onClose}\n isFormModal\n width={'95%'}\n okText={'Submit'}\n onOk={handleSubmitTool}\n footer={\n \n setVersion(e ? 'final' : 'draft')}\n checked={version === 'final'}\n checkedChildren={'final'}\n unCheckedChildren={'draft'}\n />\n \n \n \n \n \n }\n >\n \n \n \n \n \n Name: {''}\n \n \n \n >\n }\n defaultValue={toolName}\n onChange={handleChangeToolName}\n placeholder={'Enter tool name...'}\n />\n \n \n \n \n\n \n \n Output:\n \n \n \n\n {outputProps && (\n \n )}\n\n {OUTPUT_TYPES.find(\n outputType => outputType.value === selectedOutput\n ).showAdvanceSettings && (\n \n setShowAdvancedSettings(!showAdvancedSettings)}\n type=\"button\"\n >\n {showAdvancedSettings === false\n ? 'Show advanced settings'\n : 'Hide advanced settings'}\n \n \n )}\n \n {!isMobileView && (\n \n \n \n )}\n \n\n \n {outputProps && (\n \n )}\n \n\n {isMobileView && (\n \n \n \n )}\n \n \n );\n};\n\nFunctionModal.propTypes = {\n title: PropTypes.string,\n onClose: PropTypes.func,\n};\n\nexport default FunctionModal;\n","import { Spin, Tag } from 'antd';\nimport React from 'react';\nimport { DeleteFilled, EditTwoTone, ToolOutlined } from '@ant-design/icons';\n\nimport Title from 'components/Title';\nimport Button from 'components/Button';\nimport {\n StyledFlexRowRight,\n StyledSpacEvenlyFlexRow,\n} from 'styles/GenericStyledComponents';\nimport useFunctions from './hooks';\nimport { stringLocaleCompare } from 'utils/stringManipulation';\nimport FunctionModal from './FunctionModal';\nimport CodeBlock from 'components/CodeBlock';\nimport { cssVariables } from 'styles/root';\nimport { StyledFunctionsTable } from './StyledComponents';\nimport ConfirmDelete from 'components/Modals/ConfirmDelete';\n\nconst Functions = () => {\n const {\n allAITools,\n isPageReady,\n isToolModalOpen,\n selectedTool,\n handleClickDelete,\n handleViewTool,\n handleAddTool,\n handleCloseToolModal,\n handleDeleteTool,\n handleCloseDeleteModal,\n } = useFunctions();\n\n const toolColumns = [\n {\n title: 'Name',\n dataIndex: 'name',\n align: 'center',\n width: '12%',\n sorter: {\n compare: (a, b) => stringLocaleCompare(a.name, b.name),\n },\n responsive: ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'],\n },\n {\n title: 'Description',\n dataIndex: 'description',\n width: '10%',\n align: 'center',\n responsive: ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'],\n },\n {\n title: 'Parameters',\n dataIndex: 'parametersInTable',\n align: 'center',\n width: '30%',\n render: code => {\n return ;\n },\n responsive: ['xxl', 'xl', 'lg', 'md'],\n },\n {\n title: 'Output',\n dataIndex: 'outputInTable',\n align: 'center',\n width: '30%',\n render: code => {\n return ;\n },\n responsive: ['xxl', 'xl', 'lg', 'md'],\n },\n {\n title: 'Version',\n dataIndex: 'version',\n align: 'center',\n width: '8%',\n render: version => {\n return (\n {version} \n );\n },\n responsive: ['xxl', 'xl', 'lg', 'md', 'sm'],\n },\n {\n title: 'Action',\n dataIndex: 'action',\n align: 'center',\n width: '12%',\n render: (_, tool) => {\n return (\n \n handleViewTool(tool)} />\n handleClickDelete(tool)}\n style={{ color: cssVariables.red10 }}\n />\n \n );\n },\n responsive: ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'],\n },\n ];\n\n return (\n \n \n \n }\n onClick={handleAddTool}\n />\n \n \n {isToolModalOpen ? (\n \n ) : null}\n {selectedTool.toDelete ? (\n \n ) : null}\n \n );\n};\n\nexport default Functions;\n","import { useState, useEffect, useContext, useMemo } from 'react';\nimport { message } from 'antd';\n\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport {\n CLOSE_WEBSITE_EDITOR,\n SET_WEBSITE_SELECTED_PAGES,\n UPDATE_WEBSITE,\n UPDATE_WEBSITE_EDITOR_WEBSITE,\n} from 'store/action';\nimport { GET_DATA_ERROR } from 'constants/error';\nimport {\n isWebsiteEditorOpenSelector,\n websiteEditorAnswerSelector,\n websiteSelectedPageselector,\n} from 'selectors/bot/answers';\nimport useSelector from 'store/useSelector';\nimport { INITIAL_SELECTED_PAGE_LIST } from 'constants/answerbank/website';\n\nconst useWebsiteEditor = () => {\n const [state, dispatch] = useContext(Context);\n const websiteEditorAnswer = useSelector(websiteEditorAnswerSelector);\n const selectionState = useSelector(websiteSelectedPageselector);\n const isOpen = useSelector(isWebsiteEditorOpenSelector);\n const { sentinel, token } = state;\n\n const [confirmLoading, setConfirmLoading] = useState(false);\n const [downloadLoading, setDownloadLoading] = useState(false);\n const [error, setError] = useState(null);\n const [isFullScreen, setFullScreen] = useState(false);\n const [selectedPageList, setSelectedPageList] = useState(\n INITIAL_SELECTED_PAGE_LIST\n );\n const [showAdvanceSettings, setShowAdvanceSettings] = useState(false);\n const [advanceSettingsState, setAdvanceSettingsState] = useState({\n selectedUploadingMethod: 'load',\n timeoutInSecond: 60,\n });\n\n const websiteList = useMemo(\n () => websiteEditorAnswer?.scrapingInfo,\n [websiteEditorAnswer]\n );\n\n const scrollIntoWarningView = () => {\n setTimeout(() => {\n const target = document.getElementsByClassName('ant-alert');\n if (target && target[0]) {\n target[0].scrollIntoViewIfNeeded();\n }\n }, 500);\n };\n\n const handleOnUpdateAnswer = async () => {\n setConfirmLoading(true);\n const archiveList = websiteList?.urlList\n ?.filter(website => !selectionState?.urlSelected?.includes(website.url))\n ?.map(website => website.url);\n\n const payload = {\n archives: archiveList,\n ver: websiteEditorAnswer.version,\n wait_until: advanceSettingsState?.selectedUploadingMethod,\n timeout: advanceSettingsState?.timeoutInSecond * 1000,\n };\n\n if (!payload?.archives || payload?.archives?.length === 0) {\n delete payload.archives;\n }\n\n try {\n const res = await apiService.changeWebsite(\n sentinel,\n websiteEditorAnswer?.id,\n token,\n payload\n );\n\n if (res.data.success) {\n dispatch({\n type: UPDATE_WEBSITE,\n payload: {\n ...(res.data.report[1] || res.data.report[0]),\n name: 'website',\n },\n });\n setConfirmLoading(false);\n handleClose();\n } else {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n } catch (error) {\n setError({ detail: error?.message || error });\n scrollIntoWarningView();\n }\n setConfirmLoading(false);\n };\n\n const handleClearFileEditorLocalState = () => {\n setFullScreen(false);\n setError(null);\n setConfirmLoading(false);\n };\n\n const handleClose = () => {\n if (isFullScreen) {\n return setFullScreen(false);\n }\n handleClearFileEditorLocalState();\n dispatch({\n type: CLOSE_WEBSITE_EDITOR,\n });\n };\n\n const handleChangeSwitch = async e => {\n try {\n dispatch({\n type: UPDATE_WEBSITE_EDITOR_WEBSITE,\n payload: {\n version: e ? 'final' : 'draft',\n },\n });\n } catch (error) {\n message.error(error.message || GET_DATA_ERROR);\n }\n };\n\n const handleDownloadFile = async () => {\n setDownloadLoading(true);\n try {\n const res = await apiService.generateFileCdnUrl(\n sentinel,\n websiteEditorAnswer.id,\n token\n );\n const cdnUrl = res.data.report[0];\n fetch(cdnUrl)\n .then(response => response.blob())\n .then(blob => {\n const link = document.createElement('a');\n const url = URL.createObjectURL(blob);\n link.href = url;\n link.download = websiteEditorAnswer.file_name;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n setDownloadLoading(false);\n })\n .catch(error => console.error('Error fetching file:', error));\n } catch (err) {\n setDownloadLoading(false);\n throw typeof err === 'string' ? err : err.message;\n }\n };\n\n const handleOpenPages = page => {\n setSelectedPageList({\n category: page.name,\n urls: websiteList?.urlList?.filter(scanned =>\n page.urlList.some(page => page.url === scanned.url)\n ),\n });\n };\n\n const handleBackToPagesList = () => {\n setSelectedPageList(INITIAL_SELECTED_PAGE_LIST);\n };\n\n useEffect(() => {\n const filteredUrlList = websiteList?.urlList?.filter(\n urlDetails => !websiteList?.archiveList?.includes(urlDetails.url)\n );\n const filteredCategoryList = websiteList?.categoryList.filter(item =>\n item.urlList.some(\n urlItem => !websiteList?.archiveList.includes(urlItem.url)\n )\n ? true\n : false\n );\n\n dispatch({\n type: SET_WEBSITE_SELECTED_PAGES,\n payload: {\n urlSelectedRowKeys: filteredUrlList?.map(row => {\n return row.key;\n }),\n urlSelected: filteredUrlList?.map(row => {\n return row.url;\n }),\n categorySelected: filteredCategoryList?.map(row => {\n return row.name;\n }),\n categorySelectedRowKeys: filteredCategoryList?.map(row => {\n return row.key;\n }),\n },\n });\n }, [websiteList]);\n\n return {\n confirmLoading,\n handleClose,\n isFullScreen,\n handleOnUpdateAnswer,\n setFullScreen,\n handleChangeSwitch,\n isOpen,\n websiteEditorAnswer,\n handleDownloadFile,\n downloadLoading,\n selectedPageList,\n selectionState,\n handleOpenPages,\n websiteList,\n handleBackToPagesList,\n dispatch,\n showAdvanceSettings,\n setShowAdvanceSettings,\n advanceSettingsState,\n setAdvanceSettingsState,\n };\n};\n\nexport default useWebsiteEditor;\n","import React, { useMemo } from 'react';\nimport {\n Tag,\n Switch,\n Descriptions,\n Table,\n Form,\n Select,\n InputNumber,\n message,\n} from 'antd';\nimport {\n CheckCircleOutlined,\n CopyOutlined,\n DownloadOutlined,\n EditOutlined,\n FileTextOutlined,\n SaveOutlined,\n} from '@ant-design/icons';\nimport {\n SpaceBetweenWrapper,\n StyledBtnAdvanceSettings,\n StyledCodeButton,\n StyledWebsiteEditor,\n} from './WebsiteEditor.styles';\nimport Modal from 'components/Modals/GenericModal';\nimport Button from 'components/Button';\nimport { DEFAULT_ANSWER_VERSION } from 'constants/answerbank/defaults';\nimport useWebsiteEditor from './hooks';\n\nimport { cssVariables } from 'styles/root';\nimport ToggleFullScreenButton from 'components/Button/ToggleFullScreenButton';\nimport { StyledFlexRowLeft } from 'styles/GenericStyledComponents';\nimport {\n handleChangeRowSelectionWebsiteCategoryTable,\n handleChangeRowSelectionWebsitePagesTable,\n renderWebsiteCategoryTableColumn,\n renderWebsitePagesTableColumn,\n} from 'store/reducers/helpers/bot/answers';\nimport { UPLOAD_METHOD_OPTIONS } from 'constants/answerbank/website';\nimport { CopyToClipboard } from 'react-copy-to-clipboard';\n\nconst WebsiteEditor = () => {\n const {\n confirmLoading,\n handleClose,\n isFullScreen,\n handleOnUpdateAnswer,\n setFullScreen,\n handleChangeSwitch,\n websiteEditorAnswer,\n isOpen,\n handleDownloadFile,\n downloadLoading,\n selectedPageList,\n selectionState,\n handleOpenPages,\n websiteList,\n handleBackToPagesList,\n dispatch,\n showAdvanceSettings,\n setShowAdvanceSettings,\n advanceSettingsState,\n setAdvanceSettingsState,\n } = useWebsiteEditor();\n const isFinalAnswer =\n websiteEditorAnswer?.version === 'final'\n ? true\n : false || websiteEditorAnswer?.version === DEFAULT_ANSWER_VERSION;\n\n const urlSelectedCount = urlDetails =>\n urlDetails?.urlList?.filter(url =>\n selectionState?.urlSelected?.includes(url.url)\n )?.length;\n\n const columns = useMemo(\n () =>\n !selectedPageList?.urls?.length\n ? renderWebsiteCategoryTableColumn(handleOpenPages, urlSelectedCount)\n : renderWebsitePagesTableColumn,\n [selectedPageList, selectionState]\n );\n\n const rowSelection = useMemo(\n () =>\n !selectedPageList?.urls?.length\n ? handleChangeRowSelectionWebsiteCategoryTable(selectionState, dispatch)\n : handleChangeRowSelectionWebsitePagesTable(\n websiteList?.categoryList,\n selectionState,\n dispatch\n ),\n [selectedPageList, selectionState, websiteList?.categoryList]\n );\n\n const renderModalTitle = () => (\n <>\n \n {'Edit Website'}\n \n {isFinalAnswer ? 'Final' : 'Draft'}{' '}\n {isFinalAnswer ? : }\n \n \n \n >\n );\n\n return (\n \n {'OK'} \n >\n }\n footer={\n \n \n \n \n \n \n \n : }\n onClick={handleOnUpdateAnswer}\n />\n
\n \n }\n >\n \n \n \n {websiteEditorAnswer.file_name}{' '}\n }\n variant=\"link\"\n loading={downloadLoading}\n />\n \n \n {websiteEditorAnswer?.scrapingInfo?.url}{' '}\n \n message.info('Copied')}\n startIcon={ }\n />\n \n \n \n \n setShowAdvanceSettings(!showAdvanceSettings)}\n type=\"button\"\n >\n {showAdvanceSettings === false\n ? 'Show advanced settings'\n : 'Hide advanced settings'}\n \n \n
\n \n {selectedPageList?.category?.charAt(0).toUpperCase() +\n selectedPageList?.category?.slice(1) || ''}{' '}\n \n \n
\n {' '}\n Last Edited:\n {websiteEditorAnswer.lastEdited}\n
\n
\n \n\n {!showAdvanceSettings && (\n \n )}\n\n {showAdvanceSettings && (\n \n \n setAdvanceSettingsState(currentState => {\n return {\n ...currentState,\n selectedUploadingMethod: event,\n };\n })\n }\n defaultValue={advanceSettingsState?.selectedUploadingMethod}\n />\n \n \n \n setAdvanceSettingsState(currentState => {\n return {\n ...currentState,\n timeoutInSecond: value,\n };\n })\n }\n />{' '}\n Second(s)\n \n \n )}\n \n \n );\n};\n\nexport default WebsiteEditor;\n","import React, { useState, useEffect } from 'react';\nimport Joyride, { ACTIONS, EVENTS, STATUS } from 'react-joyride';\nimport { WechatFilled } from '@ant-design/icons';\nimport PropTypes from 'prop-types';\nimport { Switch, Route } from 'react-router-dom';\n\nimport Layout from 'components/Layout';\nimport AnswerBank from './AnswerBank';\nimport Analytics from './Analytics';\nimport Validation from './Validation';\nimport Integration from './Integration';\nimport Settings from './Settings';\nimport ROUTES from 'constants/routes';\nimport useBotDetails from './hooks';\nimport LinkedQuestionModal from 'components/Modals/LinkedQuestionModal';\nimport ResponsePickerModal from 'components/Modals/ResponsePickerModal';\nimport AddQuestionToTestSuiteModal from 'components/Modals/AddQuestionToTestSuiteModal';\nimport { apiService } from 'services/api.service';\nimport { SET_ONBOARDING_FLAG } from 'store/action';\nimport {\n StyledCustomTooltip,\n StyledToolTipContainer,\n StyledFooterActions,\n} from './BotDetails.styles';\nimport CallbackLogs from './CallbackLog';\nimport BotOverview from './Overview';\nimport { stripUUID } from 'utils';\nimport Questions from './Questions';\nimport SimilarQuestionsModal from 'components/Modals/SimilarQuestionsModal';\nimport AnswerEditor from 'components/Modals/AnswerEditor';\nimport QuestionEditor from 'components/Modals/QuestionEditor';\nimport ConversationSimulator from './ConversationSimulator';\nimport FileEditor from 'components/Modals/FileEditor';\nimport Functions from './Functions';\nimport EmptyAnswer from 'components/EmptyAnswer';\nimport WebsiteEditor from 'components/Modals/WebsiteEditor';\n\nconst steps = [\n {\n content: (\n \n
\n
\n Test your bot\n \n
\n Awesome! Now that you have added answers, you can test your bot by\n clicking here.\n
\n
Ask relevant question that you think your customers might ask.
\n
\n ),\n target: '.test-icon',\n disableBeacon: true,\n hideFooter: true,\n placement: 'left',\n },\n];\n\nconst CustomTooltip = ({\n continuous,\n index,\n step,\n backProps,\n primaryProps,\n tooltipProps,\n}) => {\n return (\n \n \n {step.title && {step.title}
}\n {step.content}
\n \n \n {index > 0 && (\n \n BACK\n \n )}\n {continuous && (\n \n CLOSE\n \n )}\n
\n \n \n \n );\n};\n\nCustomTooltip.propTypes = {\n continuous: PropTypes.bool,\n index: PropTypes.number,\n step: PropTypes.object,\n backProps: PropTypes.object,\n primaryProps: PropTypes.object,\n tooltipProps: PropTypes.object,\n};\n\nconst BotDetails = () => {\n const {\n allAnswers,\n contentLoading,\n currentLocation,\n dispatch,\n isChatVisible,\n isFreeUser,\n graph,\n handleChatAskQuestion,\n handleShowAnswerEditorModal,\n handleCloseLinkedQuestionModal,\n handleShowLinkedQuestions,\n handleShowAddTestSuiteModal,\n handleUnlinkQuestion,\n handleSeekAnswer,\n isPageReady,\n linkedAnswerToEdit,\n loading,\n onUpdateAnswer,\n questionForTestSuite,\n sentinel,\n onboarding_flag,\n setShowAddQuestionToTestSuiteModal,\n showAddQuestionToTestSuiteModal,\n showLinkedQuestionModal,\n token,\n validationDetails,\n jid,\n selectedAnswer,\n selectedFile,\n selectedWebsite,\n questionEditor,\n contextHistory,\n setContextHistory,\n maxInteraction,\n setMaxInteraction,\n } = useBotDetails();\n const [runTour, setRunTour] = useState(false);\n const [stepIndex, setStepIndex] = useState(0);\n\n useEffect(() => {\n if (\n isPageReady &&\n allAnswers.length &&\n !onboarding_flag.includes('TestIconBot') &&\n onboarding_flag.includes('BotSideBarMenu')\n ) {\n setRunTour(true);\n }\n return () => setRunTour(false);\n }, [allAnswers, isPageReady, onboarding_flag]);\n\n const onClickCallback = async data => {\n const { action, index, type, status } = data;\n if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) {\n setRunTour(false);\n setStepIndex(0);\n if (!onboarding_flag.includes('TestIconBot')) {\n await apiService.setOnboardingFlag(\n sentinel,\n token,\n graph,\n 'TestIconBot'\n );\n dispatch({ type: SET_ONBOARDING_FLAG, payload: 'TestIconBot' });\n }\n } else if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {\n const stepIndex = index + (action === ACTIONS.PREV ? -1 : 1);\n setStepIndex(stepIndex);\n if (index === 0) {\n }\n }\n };\n\n const botDetailsRoute = `${ROUTES.BOT_DETAILS}/${stripUUID(jid)}`;\n\n return (\n <>\n \n \n \n \n \n \n \n \n \n !isPageReady && isFreeUser ? (\n \n ) : (\n \n )\n }\n />\n \n \n \n \n \n \n\n \n\n \n \n {selectedAnswer?.isOpen ? : null}\n {selectedFile?.isOpen ? : null}\n {selectedWebsite?.isOpen ? : null}\n {questionEditor?.isOpen ? : null}\n\n {showAddQuestionToTestSuiteModal && (\n setShowAddQuestionToTestSuiteModal(false)}\n />\n )}\n \n >\n );\n};\n\nexport default BotDetails;\n","import { useContext, useState, useEffect } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { Form } from 'antd';\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport { UPDATE_USER } from 'store/action';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport useSelector from 'store/useSelector';\nimport {\n impersonatedUserSelector,\n isImpersonatingSelector,\n} from 'selectors/admin';\nimport { getTokenSelector, userSelector } from 'selectors/user';\nimport { planTypeSelector } from 'selectors/plan';\n\nconst SUCCESS_MESSAGE = 'Successfully updated profile!';\n\nconst useUserProfile = () => {\n const [state, dispatch] = useContext(Context);\n const impersonatedUser = useSelector(impersonatedUserSelector);\n const isImpersonating = useSelector(isImpersonatingSelector);\n const planType = useSelector(planTypeSelector);\n const user = useSelector(userSelector);\n const token = useSelector(getTokenSelector);\n\n const history = useHistory();\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState();\n const [success, setSuccess] = useState(false);\n const { version } = state;\n const [form] = Form.useForm();\n\n useEffect(() => {\n if (token) {\n if (\n isImpersonating &&\n impersonatedUser &&\n Object.keys(impersonatedUser).length\n ) {\n form.setFieldsValue({\n name: impersonatedUser.name,\n email: impersonatedUser.email,\n });\n } else {\n form.setFieldsValue({ name: user.name, email: user.email });\n }\n }\n }, [dispatch, form, token, user]); // run only once the component is mounted\n\n const editUser = async values => {\n setLoading(true);\n const userData = {\n name: values.name,\n email: values.email,\n password: values.password,\n };\n\n try {\n const updatedUser = await apiService.updateUser(userData);\n dispatch({ type: UPDATE_USER, payload: updatedUser.data });\n setSuccess(true);\n if (error) {\n setError(null);\n }\n } catch (error) {\n setSuccess(false);\n setError(error.message || DEFAULT_ERROR_MESSAGE);\n }\n setLoading(false);\n };\n\n const onFinishFailed = val => {};\n\n const handleGoBack = () => {\n history.goBack();\n };\n\n return {\n user,\n form,\n success,\n error,\n loading,\n SUCCESS_MESSAGE,\n version,\n editUser,\n onFinishFailed,\n handleGoBack,\n history,\n planType,\n };\n};\n\nexport default useUserProfile;\n","import styled from 'styled-components';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledPageTitle = styled.div`\n text-align: left;\n width: 100%;\n margin: 1% 2%;\n & h3 {\n display: flex;\n width: 100%;\n font-size: 24px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\n\nexport const StyledContent = styled.div`\n display: flex;\n height: auto;\n background: #fff;\n padding: 30px;\n width: 100%;\n flex-direction: column;\n\n & .ant-alert {\n margin: 20px;\n }\n\n & h2 {\n font-size: 18px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\n\nexport const StyledFormButtons = styled.div`\n display: flex;\n column-gap: 10px;\n\n & button {\n width: 100%;\n }\n\n &:first-child {\n margin: 0 !important;\n }\n`;\n\nexport const StyledVersionDetailsContainer = styled.div`\n display: flex;\n height: auto;\n padding: 5px 0;\n width: 100%;\n flex-direction: column;\n font-weight: 700;\n`;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Col, Form, Row, Tag, Typography } from 'antd';\nimport Input from 'components/Input';\nimport Button from 'components/Button';\nimport Alert from 'components/Alert';\nimport useUserProfile from './hooks';\nimport {\n StyledPageTitle,\n StyledContent,\n StyledFormButtons,\n StyledVersionDetailsContainer,\n} from './UserProfile.style';\nimport {\n StyledFlexColumn,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\nimport ROUTES from 'constants/routes';\n\nconst { Title } = Typography;\n\nconst MyAccount = props => {\n const {\n user,\n form,\n success,\n error,\n loading,\n SUCCESS_MESSAGE,\n version,\n editUser,\n onFinishFailed,\n handleGoBack,\n history,\n planType,\n } = useUserProfile();\n\n const name = user.name;\n const email = user.email;\n\n return (\n \n \n \n \n User Settings \n \n \n \n \n {planType.toUpperCase()}\n \n \n \n \n \n history.push(ROUTES.SUBSCRIPTION)}\n value=\"Upgrade Subscription\"\n block={true}\n />\n \n
\n \n \n \n \n \n Version: {version}\n \n \n );\n};\n\nMyAccount.user = {\n user: PropTypes.shape({\n name: PropTypes.string,\n email: PropTypes.string,\n password: PropTypes.string,\n }),\n};\n\nexport default MyAccount;\n","import styled from 'styled-components';\nimport { Menu } from 'antd';\nimport { cssVariables } from 'styles/root';\nimport { StyledFlexColumn } from 'styles/GenericStyledComponents';\n\nexport const StyledProfileSibar = styled(Menu)`\n margin-top: 20px;\n\n & .ant-menu-item {\n padding-left: 36px !important;\n margin: 0px !important;\n font-size: 16px;\n }\n`;\n\nexport const StyledTransactionParagraph = styled.span`\n color: ${props => (props.isRed ? '#ff4d4f' : 'auto')};\n`;\n\nexport const StyledUserContentWrapper = styled(StyledFlexColumn)`\n min-width: 350px;\n`;\n","import React, { useState, useEffect } from 'react';\nimport { Layout, Menu, Progress } from 'antd';\nimport { Link } from 'react-router-dom';\nimport { snakeCase } from 'lodash';\nimport {\n DollarOutlined,\n FlagOutlined,\n KeyOutlined,\n ProfileOutlined,\n ReconciliationOutlined,\n StepBackwardOutlined,\n StepForwardOutlined,\n UserOutlined,\n} from '@ant-design/icons';\n\nimport {\n StyledFlexColumn,\n StyledFlexRowCenter,\n StyledSpaceBetweenFlexColumn,\n} from 'styles/GenericStyledComponents';\nimport { BackToBotsPage } from 'components/BackButton/BackToBotsPage';\nimport {\n StyledToggleIconWrapper,\n Wrapper,\n StyledSidebarBottom,\n StyledSidebarBottomCounts,\n StyledSidebarBottomTitle,\n StyledSidebarItemsTitle,\n} from './Sidebar.style';\nimport useSidebar from './hooks';\nimport { useHistory } from 'react-router-dom';\nimport ROUTES from 'constants/routes';\nimport { slugify } from 'utils/stringManipulation';\nimport useSelector from 'store/useSelector';\nimport {\n botPercentageSelector,\n currentBotCountSelector,\n maxBotCountSelector,\n maxTransactionCountSelector,\n planTypeSelector,\n} from 'selectors/plan';\nimport { StyledTransactionParagraph } from 'pages/UserProfile/index.style';\nimport {\n currentTransactionCountSelector,\n transactionPercentageSelector,\n trialDaysLeftSelector,\n} from 'selectors/subscription';\nimport { ZSB_CONTACT } from 'constants/outboundLinks';\nimport {\n botsExceedsLimitSelector,\n botsRemainingSelector,\n totalExceedingBotsSelector,\n} from 'selectors/bots';\nimport GenericHR from 'components/HR/GenericHR';\n\nconst { Sider } = Layout;\n\nconst MENU_LIST = [\n {\n name: 'My Account',\n icon: ,\n path: ROUTES.USER_PROFILE,\n },\n {\n name: 'Activity Logs',\n icon: ,\n path: ROUTES.ACTIVITY_LOGS,\n },\n {\n name: 'Onboarding Flags',\n icon: ,\n path: ROUTES.ONBOARDING_FLAGS,\n },\n {\n name: 'Plan and Payments',\n icon: ,\n path: ROUTES.PLAN_AND_PAYMENTS,\n },\n {\n name: 'Pricing',\n icon: ,\n path: ROUTES.SUBSCRIPTION,\n },\n];\n\nconst API_KEY_MENU = [\n {\n name: 'API Key',\n icon: ,\n path: ROUTES.API_KEY,\n },\n];\n\nconst UserSidebar = () => {\n const botsExceedsLimit = useSelector(botsExceedsLimitSelector);\n const botsRemaining = useSelector(botsRemainingSelector);\n const botPercentage = useSelector(botPercentageSelector);\n const currentBotCount = useSelector(currentBotCountSelector);\n const currentTransactionCount = useSelector(currentTransactionCountSelector);\n const maxBotCount = useSelector(maxBotCountSelector);\n const maxTransactionCount = useSelector(maxTransactionCountSelector);\n const planType = useSelector(planTypeSelector);\n const totalExceedingBots = useSelector(totalExceedingBotsSelector);\n const transactionPercentage = useSelector(transactionPercentageSelector);\n const trialDaysLeft = useSelector(trialDaysLeftSelector);\n\n const { handleToggleCollapse, showIconOnly } = useSidebar();\n const [selectedKey, setSelectedKey] = useState();\n const [menu, setMenu] = useState(MENU_LIST);\n const { push } = useHistory();\n\n useEffect(() => {\n setMenu(planType !== 'free' ? [...MENU_LIST, ...API_KEY_MENU] : MENU_LIST);\n }, [planType]);\n\n const getTransactionCountParagraph = () => {\n if (currentTransactionCount > maxTransactionCount) {\n const excessTransaction = Math.abs(\n currentTransactionCount - maxTransactionCount\n );\n return `You have gone above your transaction limit this month. Excess transaction count: ${excessTransaction} `;\n } else {\n return `You have used ${currentTransactionCount} of ${maxTransactionCount} ${\n planType === 'free' ? 'free' : ''\n } transactions per month.`;\n }\n };\n\n const renderMenu = (menu, slug) => {\n return (\n {\n push(menu.path);\n }}\n >\n {menu.name}\n \n );\n };\n\n return (\n \n \n \n \n \n
\n \n \n setSelectedKey(e?.key)}\n >\n {!!menu &&\n menu.map((menu, key) => {\n const slug = slugify(menu.name);\n return renderMenu(menu, slug);\n })}\n \n \n \n {showIconOnly ? (\n \n ) : (\n \n )}\n \n \n \n\n \n \n {planType.charAt(0).toUpperCase() + planType.slice(1)}{' '}\n {showIconOnly ? null : 'Plan'}\n \n\n {planType !== 'free' ? (\n \n {/* {showIconOnly ? null : (\n <> */}\n
\n {'Transactions'}\n \n
\n {`${currentTransactionCount}/${maxTransactionCount}`}\n \n {/* >\n )} */}\n
\n {showIconOnly ? null : (\n <>\n {trialDaysLeft > 0 ? (\n
\n You have {trialDaysLeft} day/s left on your trial!.{' '}\n \n ) : null}\n
80}\n >\n {getTransactionCountParagraph()}\n \n {planType === 'advanced' ? (\n
\n Contact us for more\n \n ) : (\n
\n Get more transactions.\n \n )}\n >\n )}\n
\n ) : null}\n \n
Bots \n
\n {`${currentBotCount}/${maxBotCount}`}\n \n
\n {showIconOnly ? null : (\n <>\n {botsExceedsLimit ? (\n
{`You have exceeded ${totalExceedingBots}`} \n ) : (\n
{`${botsRemaining} bots left. `} \n )}\n {planType === 'advanced' ? (\n
\n Contact us for more\n \n ) : (\n
{'Upgrade for more.'}\n )}\n >\n )}\n\n {planType === 'advanced' && showIconOnly ? (\n
\n Contact us\n \n ) : null}\n
\n \n \n \n \n );\n};\n\nexport default UserSidebar;\n","import styled, { css } from 'styled-components';\nimport { cssVariables } from 'styles/root';\nimport { device } from 'constants/screens';\nimport { Menu, Row } from 'antd';\n\nexport const StyledWrapper = styled.div`\n ${props =>\n props.displayAdminSideBar\n ? css`\n display: flex;\n `\n : css`\n margin: 1% 2%;\n `};\n text-align: left;\n width: 95%;\n`;\n\nexport const StyledTitleWrapper = styled(Row)`\n width: 100%;\n\n @media all and (${device.laptopL}) {\n font-size: 14px;\n }\n`;\n\nexport const StyledTitle = styled.h2`\n font-weight: 600;\n margin: auto;\n`;\n\nexport const StyledPageTitle = styled.div`\n & h3 {\n display: flex;\n width: 100%;\n font-size: 24px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\n\nexport const StyledContentWrapper = styled.div`\n ${props =>\n props.displayAdminSideBar\n ? css`\n & .ant-timeline-item-tail {\n border-left: 2px solid #ffff !important;\n }\n `\n : css`\n background: none;\n `}\n display: flex;\n width: 100%;\n flex-direction: column;\n\n ${props =>\n props.isAdmin\n ? css`\n padding: 50px;\n height: 100vh;\n overflow-x: hidden;\n `\n : css`\n height: 80vh;\n `}\n & .ant-alert {\n margin: 20px;\n }\n\n & h2 {\n font-size: 24px;\n font-weight: 500;\n }\n\n & .filter-button {\n background: #eff0fa !important;\n border: 1px solid rgba(206, 210, 234, 0.5) !important;\n border-radius: 5px;\n padding: 11px !important;\n\n font-style: normal !important;\n font-weight: 500 !important;\n font-size: 12px !important;\n line-height: 18px !important;\n color: #060045 !important;\n height: auto !important;\n }\n`;\n\nexport const StyledContentSubHeaderWrapper = styled(Row)`\n padding: 4% 0;\n`;\n\nexport const StyledButtonContainer = styled.div`\n display: flex;\n flex-direction: column;\n`;\n\nexport const StyledLogsDetailsContainer = styled.div`\n padding-top: 30px;\n overflow-x: hidden;\n height: 300px;\n overflow: auto;\n display: flex;\n`;\n\nexport const StyledUserInfoContainer = styled.p`\n line-height: 0.05;\n color: #888888;\n`;\n\nexport const StyledDetailsContainer = styled.p`\n color: rgba(0, 0, 0, 0.85);\n font-size: 14px;\n`;\n\nexport const StyledSortSelected = styled(Menu.Item)`\n background-color: ${props =>\n props.eventKey === props.sortType ? cssVariables.primaryBlue : ''};\n color: ${props => (props.eventKey === props.sortType ? '#fff' : '')}; ;\n`;\n","import { useContext, useEffect, useState } from 'react';\nimport moment from 'moment';\nimport { useLocation } from 'react-router-dom';\nimport {\n Button,\n Checkbox,\n Popover,\n Space,\n Typography,\n message,\n Radio,\n Result,\n Tag,\n} from 'antd';\nimport {\n CaretDownOutlined,\n ClearOutlined,\n ContainerOutlined,\n PlusOutlined,\n RobotOutlined,\n SearchOutlined,\n SettingOutlined,\n UserOutlined,\n InfoCircleTwoTone,\n} from '@ant-design/icons';\n\nimport { UTCToLocal, getLocalTimeString } from 'utils/dates';\nimport { apiService } from 'services/api.service';\nimport { GET_DATA_ERROR } from 'constants/error';\nimport { Context } from 'store/store';\nimport ROUTES from 'constants/routes';\nimport { StyledFlexRowRight } from 'styles/GenericStyledComponents';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\n\nconst { Text } = Typography;\nconst useActivityLogs = () => {\n const location = useLocation();\n const [activityLogs, setActivityLogs] = useState([]);\n const [isLoading, setIsLoading] = useState(false);\n const [state] = useContext(Context);\n const { token, sentinel } = state;\n const startDateInMS = new Date().setDate(new Date().getDate() - 1);\n const startDate = new Date(startDateInMS).toISOString();\n const todayInMS = new Date();\n const todayISO = new Date(todayInMS).toISOString();\n const [dateFilter, setDateFilter] = useState({\n startDate: startDate,\n endDate: todayISO,\n });\n const [selectedActivityLogs, setSelectedActivityLogs] = useState(null);\n const [showDetailsModal, setShowDetailsModal] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [filter, setFilter] = useState('all');\n const [bots, setBots] = useState(null);\n const [actions, setActions] = useState(null);\n const [activeBots, setActiveBots] = useState(null);\n const [users, setUsers] = useState(null);\n const [searchCheckedAction, setSearchCheckedAction] = useState(['']);\n const [searchCheckedBotList, setSearchCheckedBotList] = useState({\n botId: [''],\n botName: [''],\n });\n const [searchCheckedUsers, setSearchCheckedUsers] = useState(['']);\n const [totalRecords, setTotalRecords] = useState(0);\n const [numberOfRecords, setNumberOfRecords] = useState(10);\n const [pageNumber, setPageNumber] = useState(1);\n const [activityLogsState, setActivityLogsState] = useState({\n items: [],\n hasMore: true,\n });\n const [sort, setSort] = useState('desc');\n const { user } = state;\n const isAdmin =\n user?.is_superuser && location.pathname === ROUTES.ADMIN_ACTIVITY_LOGS;\n\n const resetState = () => {\n setIsOpen(false);\n setSort('desc');\n setFilter('all');\n getActivityLogs(dateFilter.startDate, dateFilter.endDate, {\n isExecutedAlready: false,\n sort: 'desc',\n bot: [],\n bot_id: [],\n activity_type: [],\n master_id: [],\n });\n\n setSelectedActivityLogs(null);\n setSearchCheckedBotList({\n botId: [''],\n botName: [''],\n });\n setSearchCheckedUsers(['']);\n setSearchCheckedAction(['']);\n };\n\n const loadingTemplate = (\n \n Loading... \n
\n );\n\n const endMessageTemplate = (\n \n All logs are displayed. \n
\n );\n\n const setParams = (start_date, end_date, parameter) => {\n let params = {\n master_id: location?.state ? [location?.state?.user_id] : null,\n start_date,\n end_date,\n all: location.pathname === ROUTES.ADMIN_ACTIVITY_LOGS,\n sort: {\n datetime: parameter.sort,\n },\n };\n\n if (\n parameter.bot &&\n parameter.bot_id &&\n parameter.activity_type &&\n parameter.master_id\n ) {\n return params;\n }\n\n if (\n searchCheckedBotList?.botId?.length > 1 &&\n searchCheckedBotList?.botName?.length > 1\n ) {\n params = {\n ...params,\n bot: searchCheckedBotList.botName,\n bot_id: searchCheckedBotList.botId,\n };\n }\n if (searchCheckedAction.length > 1) {\n params = {\n ...params,\n activity_type: searchCheckedAction,\n };\n }\n\n if (searchCheckedUsers.length > 1) {\n params = {\n ...params,\n all: false,\n master_id: searchCheckedUsers,\n };\n }\n\n return params;\n };\n\n const getActivityLogs = async (start_date, end_date, param) => {\n setIsLoading(true);\n try {\n let noOfRecords = 0;\n const params = setParams(start_date, end_date, param);\n if (!param.isExecutedAlready || param.isExecutedAlready === false) {\n noOfRecords = numberOfRecords;\n setPageNumber(1);\n setNumberOfRecords(noOfRecords);\n } else if (param.isExecutedAlready === true) {\n noOfRecords =\n totalRecords >= numberOfRecords\n ? 10 * (pageNumber + 1)\n : numberOfRecords;\n setNumberOfRecords(noOfRecords);\n setPageNumber(pageNumber + 1);\n }\n const res = await apiService.getActivityLogs(\n sentinel,\n token,\n params,\n noOfRecords\n );\n\n if (!res.data.success || !res.data.report || !res.data.report[0]) {\n throw new Error(GET_DATA_ERROR);\n }\n setActivityLogs(res?.data?.report[0]?.hits?.hits);\n setTotalRecords(res?.data?.report[0]?.hits?.total.value);\n setActivityLogsState({\n items: res?.data?.report[0]?.hits?.hits,\n hasMore:\n res?.data?.report[0]?.hits?.total.value >\n res?.data?.report[0]?.hits?.hits.length,\n });\n fetchFilters(params.master_id, params);\n } catch (err) {\n console.log('error:', err);\n setIsLoading(false);\n }\n };\n\n const parseToStartAndEndOfDayISODate = dateFilter => {\n const { startDate, endDate } = dateFilter;\n\n var validStartDate = new Date(\n new Date(startDate).setHours(0, 0, 0, 0)\n ).toISOString();\n var validEndDate = new Date(\n new Date(endDate).setHours(23, 59, 59, 59)\n ).toISOString();\n\n return { validStartDate, validEndDate };\n };\n\n const formatDateTime = dateTime => {\n const localDate = UTCToLocal(new Date(dateTime));\n return getLocalTimeString(localDate);\n };\n\n const handleDateFilterChange = (start_date, end_date) => {\n const startDateConverted = new Date(start_date);\n const endDate = new Date(end_date);\n setDateFilter({\n startDate: startDateConverted,\n endDate: endDate,\n });\n };\n\n const handleModalOpening = (\n activityDate,\n activityAction,\n bot,\n message,\n user,\n planType,\n id\n ) => {\n if (activityDate || activityAction) {\n setSelectedActivityLogs({\n activityDate,\n activityAction,\n bot,\n message,\n user,\n planType,\n id,\n });\n } else {\n setSelectedActivityLogs(null);\n }\n setShowDetailsModal(!showDetailsModal);\n };\n\n const handleModalClosing = () => {\n setSelectedActivityLogs(null);\n setShowDetailsModal(false);\n };\n\n const setSelectedActivityLogDetails = logDetails => {\n handleModalOpening(\n formatDateTime(moment(logDetails.datetime)),\n logDetails.activity_action,\n logDetails.bot,\n assignActivityMessage(logDetails),\n logDetails.user?.email,\n logDetails.data.plan_type,\n logDetails.bot_id\n );\n };\n\n const assignActivityMessage = (logDetails, isExportFile) => {\n switch (logDetails.activity_type) {\n case 'add_bot':\n return logDetails.data.desc\n ? `Name: ${logDetails.data.name} ` +\n (isExportFile ? `` : `\\n`) +\n `Description: ${logDetails.data.desc}`\n : `Name: ${logDetails.data.name}`;\n case 'change_integration':\n case 'create_integration':\n return (\n `Name: ${logDetails?.data?.identifier} ` +\n (isExportFile ? `` : `\\n`) +\n `Type: ${logDetails?.data?.int_type}`\n );\n default:\n return logDetails?.data?.text;\n }\n };\n\n const handleDownloadActivityLogs = () => {\n const titleColumn = [\n 'Activity Date,',\n 'User,',\n 'Action,',\n 'Bot Name,',\n 'Current Bot Name,',\n 'Message,',\n '\\n',\n ];\n const fileData = activityLogs.map(item => {\n const message = assignActivityMessage(item._source, true)\n ? `Value: ${assignActivityMessage(item._source, true)}`\n : 'N/A';\n const userInfo = item._source.user ? item._source.user?.email : '';\n const newBotName = activeBots?.find(\n activeBot =>\n activeBot.jid === item._source.bot_id &&\n activeBot?.context?.name !== item._source.bot\n )\n ? activeBots?.find(activeBot => activeBot.jid === item._source.bot_id)\n ?.context?.name\n : '';\n\n return (\n moment(UTCToLocal(new Date(item._source.datetime))).format(\n 'YYYY-MM-DD HH:mm'\n ) +\n ',' +\n userInfo +\n ',' +\n item._source.activity_action +\n ',' +\n item._source.bot +\n ',' +\n newBotName +\n ',' +\n message +\n '\\n'\n );\n });\n const blob = new Blob(['\\ufeff', ...titleColumn, ...fileData], {\n type: 'text/csv',\n });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.download = 'zsb-activity-logs.csv';\n link.href = url;\n link.click();\n message.success('Activity logs is exported successfully');\n };\n\n const handleChangeFilter = e => {\n setFilter(e.target.value);\n if (e.target.value === 'all') {\n resetState();\n }\n };\n\n const handleChangeActivityDateSorting = sort => {\n setSort(sort);\n getActivityLogs(dateFilter.startDate, dateFilter.endDate, {\n sort,\n isExecutedAlready: true,\n });\n };\n\n const handleSearch = (e, botName) => {\n if (filter === 'action') {\n setSearchCheckedAction(\n handleCheckedOnSearch(\n searchCheckedAction.filter(value => value !== e.target?.value),\n e.target.value,\n e.target.checked\n )\n );\n } else if (filter === 'botName') {\n const selectedBot = {\n botId: handleCheckedOnSearch(\n searchCheckedBotList?.botId?.filter(\n value => value !== e.target?.value\n ),\n e.target.value,\n e.target.checked\n ),\n botName: handleCheckedOnSearch(\n searchCheckedBotList?.botName?.filter(value => value !== botName),\n botName,\n e.target.checked\n ),\n };\n setSearchCheckedBotList(selectedBot);\n } else if (filter === 'users') {\n setSearchCheckedUsers(\n handleCheckedOnSearch(\n searchCheckedUsers.filter(value => value !== e.target?.value),\n e.target.value,\n e.target.checked\n )\n );\n }\n };\n\n const handleCheckedOnSearch = (filteredData, value, checked) => {\n if (!checked) {\n return filteredData;\n } else {\n return [...filteredData, value];\n }\n };\n\n const fetchFilters = async (master_id, params) => {\n try {\n const res = await apiService.getActivityLogsFilters(\n sentinel,\n token,\n master_id,\n location.pathname === ROUTES.ADMIN_ACTIVITY_LOGS,\n params\n );\n\n if (!res.data.success || !res.data.report || !res.data.report[0]) {\n throw new Error(GET_DATA_ERROR);\n }\n setIsLoading(false);\n setBots(\n res.data.report[0]?.aggregations?.bots?.buckets.map(bot => {\n return {\n name: bot.key[0],\n id: bot.key[1],\n status: res.data?.report[1]?.find(\n activeBots => activeBots.jid === bot.key[1]\n )\n ? 'Active'\n : 'Deleted',\n };\n })\n );\n setActiveBots(res.data?.report[1]);\n setActions(res.data.report[0]?.aggregations?.types?.buckets);\n setUsers(res.data.report[0]?.aggregations?.masters?.buckets);\n } catch (err) {\n console.log('error:', err);\n setIsLoading(false);\n }\n };\n\n const handleChangePopOver = newOpen => {\n setIsOpen(newOpen);\n };\n\n const filterTitle = (\n \n All \n {actions && (\n \n \n Action Types \n \n )}\n {bots && !isAdmin && (\n \n \n Bot List \n \n )}\n {isAdmin && (\n \n \n Users\n \n )}\n \n );\n\n const renderNewBotName = (id, name) => {\n return activeBots?.find(\n activeBot => activeBot.jid === id && activeBot?.context?.name !== name\n ) ? (\n \n Current Bot Name:{' '}\n \n {\n activeBots?.find(activeBot => activeBot.jid === id)?.context\n ?.name\n }\n \n >\n }\n >\n \n \n ) : null;\n };\n\n const filterList = (\n \n \n \n {filter === 'all' && (\n }\n title=\"All logs are displayed.\"\n style={{ width: '100%', textAlign: 'center' }}\n />\n )}\n\n {filter === 'botName' &&\n !isAdmin &&\n bots?.map((bot, idx) => (\n handleSearch(e, bot.name)}\n value={bot.id}\n checked={\n searchCheckedBotList?.botId?.find(\n checkedBot => checkedBot === bot.id\n ) &&\n searchCheckedBotList?.botName?.find(\n checkedBot => checkedBot === bot.name\n )\n }\n key={idx}\n >\n {bot.name}{' '}\n {bot.status === 'Deleted' && (\n \n \n \n )}\n {renderNewBotName(bot.id, bot.name)}\n \n ))}\n\n {filter === 'action' &&\n actions?.map(action => (\n \n {action.key[1]}\n \n ))}\n\n {filter === 'users' &&\n users?.map(users => (\n \n {users.key[1]}\n \n ))}\n {\n setIsOpen(false);\n setSort('desc');\n getActivityLogs(dateFilter.startDate, dateFilter.endDate, {\n sort: 'desc',\n });\n }}\n style={{\n width: '40%',\n position: 'absolute',\n left: '10px',\n bottom: 0,\n marginBottom: '15px',\n }}\n hidden={filter === 'all' || !actions || !bots}\n >\n Search\n \n {\n setIsOpen(false);\n setSort('asc');\n resetState();\n }}\n style={{\n width: '40%',\n position: 'absolute',\n right: '10px',\n bottom: 0,\n marginBottom: '15px',\n }}\n hidden={filter === 'all' || !actions || !bots}\n >\n Clear\n \n \n \n
\n );\n\n const filters = (\n \n \n \n \n Add Filter\n \n \n \n \n );\n\n const fetchNextScroll = () => {\n const { validStartDate, validEndDate } =\n parseToStartAndEndOfDayISODate(dateFilter);\n setTimeout(() => {\n getActivityLogs(validStartDate, validEndDate, {\n isExecutedAlready: true,\n sort: sort,\n });\n }, 500);\n };\n\n useEffect(() => {\n if (sentinel && token) {\n resetState();\n }\n }, [sentinel, token]);\n\n return {\n activityLogs,\n dateFilter,\n selectedActivityLogs,\n showDetailsModal,\n isLoading,\n filters,\n activityLogsState,\n sort,\n loadingTemplate,\n endMessageTemplate,\n isAdmin,\n formatDateTime,\n handleDateFilterChange,\n setSelectedActivityLogDetails,\n handleModalClosing,\n handleModalOpening,\n handleDownloadActivityLogs,\n handleChangeFilter,\n getActivityLogs,\n handleSearch,\n setSort,\n handleChangeActivityDateSorting,\n fetchNextScroll,\n parseToStartAndEndOfDayISODate,\n activeBots,\n renderNewBotName,\n };\n};\n\nexport default useActivityLogs;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Descriptions, Tag } from 'antd';\nimport Modal from 'components/Modals/GenericModal';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\nimport { StyledFlexRowRight } from 'styles/GenericStyledComponents';\n\nconst ActivityLogsModal = ({\n selectedActivityLogs,\n showDetailsModal,\n onOk,\n onCancel,\n status,\n renderNewBotName,\n}) => {\n return (\n ,\n ]}\n closable={false}\n title={Activity Details }\n >\n \n {status} \n \n \n \n {selectedActivityLogs?.activityDate}\n \n\n {selectedActivityLogs?.user && (\n \n {selectedActivityLogs?.user}\n \n )}\n\n \n {selectedActivityLogs?.activityAction}\n \n\n {selectedActivityLogs?.bot && selectedActivityLogs?.bot !== 'N/A' && (\n \n {selectedActivityLogs?.bot}{' '}\n {renderNewBotName(\n selectedActivityLogs.id,\n selectedActivityLogs.bot\n )}\n \n )}\n\n {!selectedActivityLogs?.message ||\n (selectedActivityLogs?.message !==\n ('null' || null || 'undefined' || undefined) && (\n \n \n {selectedActivityLogs?.activityAction} \n
\n {selectedActivityLogs?.message && (\n <>\n Value:
\n \n {selectedActivityLogs?.message}\n \n >\n )}\n \n ))}\n\n {selectedActivityLogs?.planType && (\n \n {selectedActivityLogs?.planType}\n \n )}\n \n \n );\n};\n\nActivityLogsModal.propTypes = {\n onCancel: PropTypes.func,\n onOk: PropTypes.func,\n selectedActivityLogs: PropTypes.object,\n showDetailsModal: PropTypes.bool,\n status: PropTypes.string,\n renderNewBotName: PropTypes.func,\n};\n\nexport default ActivityLogsModal;\n","import styled from 'styled-components';\nimport { Layout, Divider } from 'antd';\nimport Button from 'components/Button';\n\nexport const StyledLayoutSider = styled(Layout.Sider)`\n box-shadow: inset -1px 0px 0px #f1f4f5;\n height: 100%;\n padding-top: 2rem;\n\n & .sidebar-menu {\n width: 199px !important;\n\n &.ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal)\n .ant-menu-item-selected {\n background-color: #5aa3cb1a;\n border-right: 5px solid #1890ff;\n }\n }\n`;\n\nexport const StyledDivider = styled(Divider)`\n margin: 0 0.5rem 0 1.2rem;\n background-color: #162831;\n width: 80%;\n min-width: 80%;\n`;\n\nexport const StyledTitle = styled.h3`\n color: white;\n padding: 1rem;\n margin: 1rem 0 1rem 1rem;\n`;\n\nexport const StyledBackButton = styled(Button)`\n width: fit-content;\n font-size: 16px;\n padding: 5px;\n margin: 0 1rem 1rem 1rem;\n background-color: inherit;\n color: #ffffffa6;\n\n &:hover {\n color: #ffffffa6;\n background-color: inherit;\n }\n`;\n","import React, { useState, useEffect, useMemo } from 'react';\nimport { Link, useLocation, useHistory } from 'react-router-dom';\nimport PropTypes from 'prop-types';\nimport { Avatar, Menu } from 'antd';\nimport { ArrowLeftOutlined } from '@ant-design/icons';\nimport {\n StyledLayoutSider,\n StyledDivider,\n StyledTitle,\n StyledBackButton,\n} from './AdminSidebar.style';\nimport { MINIMUM_RECOMMENDED_WIDTH } from 'constants/screens';\n\nconst AdminSidebar = ({ theme, route, title }) => {\n const [activeMenu, setActiveMenu] = useState(route[0].key);\n const [windowWidth, setWindowWidth] = useState(window.innerWidth);\n const currentRouteLocation = useLocation();\n let history = useHistory();\n\n const detectSize = () => {\n setWindowWidth(window.innerWidth);\n };\n\n const showIconOnly = useMemo(() => {\n // the window size will decide to showIconOnly or not\n return windowWidth < MINIMUM_RECOMMENDED_WIDTH;\n }, [windowWidth]);\n\n useEffect(() => {\n setActiveMenu(currentRouteLocation.pathname);\n }, [currentRouteLocation.pathname]);\n\n useEffect(() => {\n window.addEventListener('resize', detectSize);\n\n return () => {\n window.removeEventListener('resize', detectSize);\n };\n }, [windowWidth]);\n\n return (\n \n {showIconOnly ? (\n history.push('/admin')}\n value=\"\"\n startIcon={\n \n }\n variant=\"link\"\n />\n ) : (\n history.push('/admin')}\n value=\"Back to Home\"\n startIcon={ }\n variant=\"link\"\n />\n )}\n \n {showIconOnly ? (\n \n {title[0].toUpperCase()}\n \n ) : (\n \n {title}\n \n )}\n \n \n {route.map(link => {\n return (\n \n \n {link.name} \n \n \n );\n })}\n \n \n );\n};\n\nAdminSidebar.propTypes = {\n theme: PropTypes.string,\n title: PropTypes.string,\n route: PropTypes.arrayOf(\n PropTypes.shape({\n name: PropTypes.string,\n path: PropTypes.string,\n component: PropTypes.func,\n key: PropTypes.string,\n icon: PropTypes.string,\n })\n ),\n};\n\nexport default AdminSidebar;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Select, Modal } from 'antd';\n\nconst { Option } = Select;\n\nconst UpdateUserModal = ({\n showUpdateBotset,\n setShowUpdateBotset,\n handleChange,\n selectedUser,\n}) => {\n const selectedUserPlan = selectedUser?.context?.plan_type;\n return (\n {\n setShowUpdateBotset(false);\n }}\n onCancel={() => {\n setShowUpdateBotset(false);\n }}\n >\n {selectedUserPlan ? (\n \n
\n Current Plan:{' '}\n {selectedUserPlan.charAt(0).toUpperCase() +\n selectedUserPlan.slice(1)}\n
\n
\n Free \n Basic \n Advanced \n \n
\n ) : (\n No available data for this user.
\n )}\n \n );\n};\n\nUpdateUserModal.propTypes = {\n showUpdateBotset: PropTypes.bool.isRequired,\n setShowUpdateBotset: PropTypes.func.isRequired,\n handleChange: PropTypes.func.isRequired,\n selectedUser: PropTypes.object,\n};\n\nexport default UpdateUserModal;\n","import React, { useCallback, useState, useContext, useEffect } from 'react';\nimport { Descriptions, message, Modal, Layout, Spin, Col } from 'antd';\nimport { useHistory } from 'react-router-dom';\nimport PropTypes from 'prop-types';\nimport moment from 'moment';\nimport { EditOutlined, SaveOutlined } from '@ant-design/icons';\n\nimport Button from 'components/Button';\nimport DiGraph from 'components/DiGraph';\nimport UpdateUserModal from '../UpdateUserModal';\nimport AdminSidebar from '../../AdminSidebar';\nimport {\n UserDetailsContainer,\n UserInformation,\n GraphModal,\n UserDetailsCTA,\n SelectContainer,\n} from './UserDetails.style';\nimport { Context } from 'store/store';\nimport {\n START_IMPERSONATING_USER,\n INIT_JAC,\n SHOW_DIGRAPH,\n HIDE_DIGRAPH,\n GET_ALL_VERSIONS,\n} from 'store/action';\nimport ROUTES from 'constants/routes';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport { apiService } from 'services/api.service';\nimport { getToken, setImpersonateUser } from 'services/auth.service';\nimport { stripeAPIService } from 'services/stripeAPI.service';\nimport { getGraphObject } from 'utils/apiUtils';\nimport { cssVariables } from 'styles/root';\nimport { withPrefixUUID } from 'utils';\nimport { SidebarRoutes } from 'pages/Admin/SidebarRoutes';\n\nconst { confirm } = Modal;\n\nconst UserDetails = ({ jid }) => {\n const userJID = withPrefixUUID(jid);\n const [state, dispatch] = useContext(Context);\n const {\n sentinel,\n graph,\n admin: { all_users, showDigraph, all_versions },\n } = state;\n const [showUpdateBotset, setShowUpdateBotset] = useState(false);\n const [userData, setUserData] = useState(null);\n const [userObject, setUserObject] = useState(null);\n const [selectedUser] = all_users.filter(data => data.jid === userJID);\n const token = getToken();\n const history = useHistory();\n const [loading, setloading] = useState(false);\n const [editVersion, setEditVersion] = useState(false);\n const [selectedVersion, setSelectedVersion] = useState(null);\n const [currentVersion, setCurrentVersion] = useState(null);\n\n const getAllVersions = async () => {\n setEditVersion(false);\n const allVersions = await apiService.getVersions(sentinel, token);\n if (!allVersions || !allVersions.data) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n\n const versionData = allVersions?.data.report[0];\n setCurrentVersion(versionData.current);\n const allVersionsData = Object.entries(versionData.versions).map(\n version => ({\n version: version[0],\n })\n );\n await dispatch({\n type: GET_ALL_VERSIONS,\n payload: allVersionsData,\n });\n };\n\n useEffect(() => {\n setloading(true);\n const getUser = async () => {\n try {\n const userObject = await getGraphObject(userJID, token);\n if (userObject?.data?.active_gph_id) {\n if (sentinel && token) {\n const botSet = await apiService.getBotSet(\n userObject.data.active_gph_id,\n sentinel,\n token\n );\n setSelectedVersion(botSet.data.report[0].context.features.version);\n setUserData(botSet.data.report[0]);\n }\n }\n\n getAllVersions();\n setUserObject(userObject.data);\n setloading(false);\n } catch (error) {\n setloading(false);\n message.error(GET_DATA_ERROR);\n }\n };\n\n getUser();\n }, [userJID, sentinel, token]);\n\n const loadUserData = async (sentinel, graph) => {\n try {\n const planData = await apiService.init(sentinel, graph, token);\n await dispatch({\n type: INIT_JAC,\n payload: {\n plan: planData.data.report[0].context,\n subscription: planData.data?.report[1]?.context || {},\n },\n });\n } catch (error) {\n throw new Error(GET_DATA_ERROR);\n }\n };\n\n const handleShowDigraph = () => {\n dispatch({\n type: SHOW_DIGRAPH,\n });\n };\n\n const handleCloseDigraph = () => {\n dispatch({\n type: HIDE_DIGRAPH,\n });\n };\n\n const handleImpersonateClick = async () => {\n if (userData) {\n const userActiveGraph = userObject?.active_gph_id || null;\n const userType = userObject?.j_type || null;\n const userDataPayload = {\n impersonated_user: {\n graph: userActiveGraph,\n ...userObject,\n name: selectedUser.name,\n email: selectedUser.user,\n },\n };\n\n setImpersonateUser(graph, JSON.stringify(userDataPayload));\n if (userActiveGraph && userType !== 'super_master') {\n await dispatch({\n type: START_IMPERSONATING_USER,\n payload: userDataPayload,\n });\n\n await loadUserData(sentinel, userActiveGraph, null, token);\n\n history.push(ROUTES.BOTS_PAGE);\n } else {\n return message.error('Sorry! Graph data not available.', 3);\n }\n } else {\n return message.error('Sorry! Graph data not available.', 3);\n }\n };\n\n const handleActivityLogsClick = () => {\n history.push({\n pathname: ROUTES.ADMIN_ACTIVITY_LOGS,\n state: { user_id: userJID },\n });\n };\n\n const updatePlan = useCallback(\n async plan => {\n const result = await stripeAPIService.overrideCustomerPlan(\n userObject?.active_gph_id,\n sentinel,\n token,\n plan\n );\n const [botSet] = result.data.report.filter(\n report => report.name === 'botset'\n );\n setUserData(botSet);\n\n if (result.data.success && result.data.report.length > 0) {\n setShowUpdateBotset(false);\n message.success(`Plan updated to ${plan}!`, 3);\n } else {\n message.error('Sorry! Something went wrong updating user plan.', 3);\n }\n },\n [sentinel, token, userObject?.active_gph_id]\n );\n\n const handleChange = useCallback(\n async value => {\n confirm({\n content: Update plan to {value}?
,\n onOk() {\n updatePlan(value);\n },\n onCancel() {\n console.log('Cancel');\n },\n });\n },\n [updatePlan]\n );\n\n const handleSyncSentinel = async () => {\n setloading(true);\n try {\n const result = await apiService.syncSentinelGlobal(\n selectedUser.jid,\n token\n );\n if (result.data.success) {\n if (result?.data?.report.length > 0) {\n setUserObject(result?.data?.report[0]);\n }\n setloading(false);\n return message.success(`Successfully done`, 3);\n } else {\n setloading(false);\n return message.error('Sorry! Something went wrong while syncing.', 3);\n }\n } catch (error) {\n setloading(false);\n return message.error('Sorry! Something went wrong while syncing.', 3);\n }\n };\n\n const handleSetVersion = async () => {\n setloading(true);\n try {\n const result = await apiService.updateUserVersion(\n sentinel,\n token,\n {\n ver: selectedVersion,\n },\n userObject.active_gph_id\n );\n\n if (result.data.success) {\n if (result?.data?.report.length > 0) {\n const userObject = await getGraphObject(userJID, token);\n if (userObject?.data?.active_gph_id) {\n if (sentinel && token) {\n const botSet = await apiService.getBotSet(\n userObject.data.active_gph_id,\n sentinel,\n token\n );\n\n if (botSet.data?.report && botSet.data?.report[0]) {\n setSelectedVersion(\n botSet.data.report[0].context.features.version\n );\n setUserData(botSet.data.report[0]);\n }\n }\n }\n }\n setloading(false);\n setEditVersion(false);\n return message.success(`Successfully updated.`, 3);\n } else {\n setloading(false);\n setEditVersion(false);\n return message.error('Sorry! Something went wrong while syncing.', 3);\n }\n } catch (err) {\n setloading(false);\n setEditVersion(false);\n throw err;\n }\n };\n\n const handleChangeVersion = e => {\n setSelectedVersion(e);\n };\n\n if (!selectedUser) {\n return null;\n }\n\n return (\n \n \n \n \n {!selectedUser.is_superuser ? (\n \n {' '}\n \n \n ) : null}\n \n setShowUpdateBotset(true)}\n value=\"Update User\"\n variant=\"primary\"\n // style={{ width: '150px', marginRight: 10 }}\n full\n />\n \n \n \n \n \n \n \n \n \n \n \n \n {selectedUser.user}\n \n \n {selectedUser.name}\n \n \n {moment(selectedUser.created_date).format(\n 'MMMM D, YYYY, h:mm a'\n )}\n \n \n {selectedUser.jid}\n \n \n {userObject?.active_gph_id}\n \n \n {userObject?.alias_map['active:sentinel']}\n {userObject?.active_snt_id && (\n <>\n ({userObject?.active_snt_id})\n >\n )}\n \n \n \n {userObject?.spawned_walker_ids > 0\n ? userObject?.spawned_walker_ids.toString()\n : 'None'}\n \n \n {userData?.context.plan_type}\n \n \n {userData?.context.payment_status}\n \n \n {userData?.context.current_bot_count}\n \n \n {userData?.context.current_transaction_count}\n \n \n {userData?.context.max_bot_count}\n \n \n {userData?.context.max_txn_count}\n \n \n {!editVersion && (\n <>\n {userData?.context.features.version}{' '}\n }\n onClick={() => setEditVersion(true)}\n style={{\n marginLeft: '10px',\n backgroundColor: 'none',\n }}\n />\n >\n )}\n\n {editVersion && (\n <>\n \n option.children\n .toLowerCase()\n .indexOf(input.toLowerCase()) >= 0\n }\n >\n {all_versions.map((versionDetails, idx) => {\n return (\n \n {versionDetails.version}{' '}\n {currentVersion === versionDetails.version &&\n `(current)`}\n \n );\n })}\n \n }\n onClick={handleSetVersion}\n />\n >\n )}\n \n \n \n \n \n \n \n \n \n \n );\n};\n\nUserDetails.propTypes = {\n jid: PropTypes.string.isRequired,\n};\n\nexport default UserDetails;\n","import styled from 'styled-components';\nimport { Table, Layout, Input, Modal } from 'antd';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\n\nconst { Search } = Input;\n\nexport const StyledWrapper = styled(Layout)`\n display: flex;\n width: 100%;\n`;\n\nexport const StyledModal = styled(Modal)`\n & .ant-modal-close {\n display: none;\n }\n > .ant-modal-close {\n display: none;\n }\n color: blue;\n font-size: 24px;\n`;\n\nexport const StyledTable = styled(Table)`\n border-radius: 16px;\n\n & td {\n border-right: 1px solid #33363c1a;\n }\n\n & tr {\n border-top: 1.5px solid #33363c1a;\n border-bottom: 1.5px solid #33363c1a;\n }\n`;\n\nexport const StyledTableContainer = styled.div`\n padding: 3%;\n width: 100%;\n overflow-y: auto;\n height: 100vh;\n\n &::-webkit-scrollbar {\n width: 10px;\n margin-right: 15px;\n }\n\n &::-webkit-scrollbar-thumb {\n background: #cccccc;\n border-radius: 10px;\n margin-right: 15px;\n border: 2px solid #f9f9f9;\n }\n`;\n\nexport const StyledNavigationContainer = styled.div`\n display: flex;\n padding: 0 0 2rem 0;\n gap: 1rem;\n`;\n\nexport const StyledButton = styled(Button)`\n background-color: ${cssVariables.primaryBlueHover};\n color: ${cssVariables.primaryBlue};\n width: 97px;\n\n :hover {\n background-color: ${cssVariables.primaryBlue};\n color: ${cssVariables.primaryBlueHover};\n width: 97px;\n }\n`;\n\nexport const StyledSearch = styled(Search)`\n margin: 20px 0;\n\n > .ant-input-search\n .ant-input-group\n .ant-input-affix-wrapper:not(:last-child) {\n border-top-left-radius: 12px;\n border-bottom-left-radius: 12px;\n }\n\n &\n .ant-input-search\n > .ant-input-group\n > .ant-input-group-addon:last-child\n .ant-input-search-button {\n border-radius: 0 12px 12px 0;\n }\n`;\n\nexport const StyledTitle = styled.h2`\n font-weight: 600;\n margin: auto;\n`;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Dropdown, Menu } from 'antd';\nimport { DownOutlined } from '@ant-design/icons';\nimport { Link } from 'react-router-dom';\nimport moment from 'moment';\nimport { StyledTable } from './Users.style';\nimport { stripUUID } from 'utils';\n\nconst UsersListTable = ({\n user,\n initLoading,\n filteredUsers,\n handleOnChange,\n handleImpersonateClick,\n handleUpdateUser,\n handleActivityLogsClick,\n}) => {\n const menu = userData => (\n \n {userData.is_activated ? (\n handleUpdateUser(userData, 'deactivate')}\n >\n Deactivate User\n \n ) : (\n handleUpdateUser(userData, 'activate')}\n >\n Activate User\n \n )}\n {!userData?.is_superuser ? (\n handleImpersonateClick(userData)}>\n Impersonate User\n \n ) : null}\n \n );\n\n return (\n {\n return (\n \n {userData.name}\n \n );\n },\n sorter: (a, b) => a.name.localeCompare(b.name),\n },\n {\n title: 'Id',\n dataIndex: 'jid',\n responsive: ['lg'],\n },\n {\n title: 'Email',\n dataIndex: 'user',\n },\n {\n title: 'Status',\n dataIndex: 'is_activated',\n responsive: ['md'],\n render: value => {value ? 'Active' : 'Inactive'} ,\n },\n {\n title: 'Created Date',\n dataIndex: 'created_date',\n responsive: ['md'],\n // eslint-disable-next-line react/display-name\n render: value => (\n {moment(value).format('MMMM D, YYYY, h:mm a')} \n ),\n sorter: (a, b) => new Date(a.created_date) - new Date(b.created_date),\n },\n {\n title: 'Action',\n key: 'action',\n responsive: ['lg'],\n // eslint-disable-next-line react/display-name\n render: userData => (\n \n menu(userData)}\n type=\"primary\"\n icon={ }\n >\n Select Action\n \n
\n ),\n },\n ]}\n pagination={{\n pageSize: 10,\n total: filteredUsers.length,\n position: ['topRight'],\n }}\n onChange={handleOnChange}\n dataSource={filteredUsers}\n footer={() => `Showing ${filteredUsers.length} total users.`}\n />\n );\n};\n\nUsersListTable.propTypes = {\n user: PropTypes.object,\n initLoading: PropTypes.bool.isRequired,\n filteredUsers: PropTypes.array.isRequired,\n handleOnChange: PropTypes.func.isRequired,\n handleImpersonateClick: PropTypes.func.isRequired,\n handleUserTypeClick: PropTypes.func.isRequired,\n handleUpdateUser: PropTypes.func.isRequired,\n handleActivityLogsClick: PropTypes.func.isRequired,\n};\n\nexport default UsersListTable;\n","import React, { useState, useEffect, useContext } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { message } from 'antd';\nimport { apiService } from 'services/api.service';\nimport { setImpersonateUser } from 'services/auth.service';\nimport { Context } from 'store/store';\nimport {\n GET_ALL_USERS,\n START_IMPERSONATING_USER,\n UPDATE_USER_DATA,\n CLEAR_DIGRAPH_DATA,\n} from 'store/action';\nimport ROUTES from 'constants/routes';\nimport AdminSidebar from '../AdminSidebar';\nimport UserDetails from './UserDetails';\nimport UsersListTable from './UsersListTable';\nimport {\n StyledWrapper,\n StyledTableContainer,\n StyledSearch,\n StyledTitle,\n} from './Users.style';\nimport { useQuery } from '../../useQuery';\nimport { getGraphObject, masterUpdateUser } from 'utils/apiUtils';\nimport { stripUUID } from 'utils';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\nimport { SidebarRoutes } from '../SidebarRoutes';\n\nconst Users = () => {\n const [state, dispatch] = useContext(Context);\n const {\n admin: { all_users },\n graph,\n } = state;\n const token = useSelector(getTokenSelector);\n\n const [initLoading, setInitLoading] = useState(true);\n const [searchFilter, setSearchFilter] = useState(null);\n const [filteredUsers, setFilteredUsers] = useState([]);\n const history = useHistory();\n let filtered_users = all_users;\n const query = useQuery();\n const userJID = query.get('jid') ? stripUUID(query.get('jid')) : null;\n\n useEffect(() => {\n if (!searchFilter) {\n const allUsers = all_users.map((user, key) => {\n return {\n key,\n ...user,\n };\n });\n setFilteredUsers(allUsers);\n }\n }, [all_users, searchFilter]);\n\n useEffect(() => {\n const getuser = async () => {\n setInitLoading(true);\n const allUsers = await apiService.getMasterAllUser(\n {\n limit: 1000,\n offset: 0,\n },\n token\n );\n\n const allUsersData = allUsers.data.data;\n await dispatch({\n type: GET_ALL_USERS,\n payload: allUsersData,\n });\n };\n\n getuser();\n setInitLoading(false);\n }, [dispatch, token]);\n\n const handleOnChange = event => {\n console.log('event ', event);\n };\n\n const handleImpersonateClick = async userData => {\n const userObject = await getGraphObject(userData.jid, token);\n const userActiveGraph = userObject.data?.active_gph_id || null;\n const userType = userObject.data?.j_type || null;\n const userDataPayload = {\n impersonated_user: {\n graph: userActiveGraph,\n ...userData,\n email: userData.user,\n },\n };\n\n setImpersonateUser(graph, JSON.stringify(userDataPayload));\n if (userActiveGraph && userType !== 'super_master') {\n await dispatch({\n type: START_IMPERSONATING_USER,\n payload: userDataPayload,\n });\n\n window.location.href = ROUTES.BOTS_PAGE;\n } else {\n return message.error('Sorry! Graph data not available.', 3);\n }\n };\n\n const handleActivityLogsClick = user_id => {\n history.push({\n pathname: ROUTES.ADMIN_ACTIVITY_LOGS,\n state: { user_id: user_id },\n });\n };\n\n const handleUpdateUser = async (userData, action) => {\n if (action === 'activate') {\n const result = await masterUpdateUser(userData.id, token, {\n is_activated: true,\n });\n if (result.status === 200 && result.data === 'Update Success!') {\n message.success('User is now active!');\n dispatch({\n type: UPDATE_USER_DATA,\n payload: {\n id: userData.id,\n is_activated: true,\n },\n });\n } else {\n message.success('Ooops. Something is wrong, try again later.');\n }\n } else {\n const result = await masterUpdateUser(userData.id, token, {\n is_activated: false,\n });\n if (result.status === 200 && result.data === 'Update Success!') {\n message.success('User is now inactive!');\n dispatch({\n type: UPDATE_USER_DATA,\n payload: {\n id: userData.id,\n is_activated: false,\n },\n });\n } else {\n message.success('Ooops. Something is wrong, try again later.');\n }\n }\n };\n\n const onSearch = event => {\n const { value } = event.target;\n filtered_users = all_users.filter(au => {\n if (\n au.user.toLowerCase().includes(value.toLowerCase()) ||\n au.user.toLowerCase().includes(value.toLowerCase()) ||\n au.jid.toLowerCase().includes(value.toLowerCase())\n ) {\n return true;\n }\n return false;\n });\n\n setSearchFilter(value);\n setFilteredUsers(filtered_users);\n };\n\n useEffect(() => {\n return () => {\n dispatch({\n type: CLEAR_DIGRAPH_DATA,\n payload: null,\n });\n };\n }, [dispatch, userJID]);\n\n if (userJID) {\n return ;\n }\n\n return (\n \n \n \n Users List \n \n \n \n \n );\n};\n\nexport default Users;\n","import styled from 'styled-components';\nimport Modal from 'components/Modals/GenericModal';\nimport { CloseOutlined } from '@ant-design/icons';\nimport { Input, Button } from 'antd';\nimport { device } from 'constants/screens';\nconst { TextArea } = Input;\n\nexport const StyledModal = styled(Modal)`\n & .ant-modal-close {\n display: none;\n }\n`;\n\nexport const ModalHeader = styled.div`\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n`;\n\nexport const StyledCloseButton = styled(CloseOutlined)`\n color: #33363c;\n background: white;\n height: 20px;\n display: block;\n`;\n\nexport const StyledTitle = styled.h2`\n font-style: normal;\n font-weight: 550;\n font-size: 24px;\n color: #31353e;\n margin-top: 10px;\n`;\n\nexport const StyledNameTextfieldWrapper = styled.div`\n margin: 1rem 0;\n`;\n\nexport const StyledNameTextfield = styled(Input)`\n box-sizing: border-box;\n height: 42px;\n background: #f4f6f8;\n border: ${prop =>\n prop.error ? '1px solid red' : '1px solid rgba(65, 108, 114, 0.05)'};\n border-radius: 6px;\n flex: none;\n order: 1;\n align-self: stretch;\n flex-grow: 0;\n\n &:hover,\n :focus {\n border: ${prop =>\n prop.error ? '1px solid red' : '1px solid rgba(65, 108, 114, 0.05)'};\n }\n`;\n\nexport const StyledValueTextfieldWrapper = styled.div`\n margin: 1rem 0;\n`;\n\nexport const StyledValueTextarea = styled(TextArea)`\n padding: 1rem 0.75rem 1rem 0.75rem;\n box-sizing: border-box;\n width: 465px;\n height: 120px;\n background: #f4f6f8;\n border: ${prop =>\n prop.error ? '1px solid red' : '1px solid rgba(65, 108, 114, 0.05)'};\n border-radius: 6px;\n flex: none;\n order: 1;\n align-self: stretch;\n flex-grow: 0;\n\n &:hover,\n :focus {\n border: ${prop =>\n prop.error ? '1px solid red' : '1px solid rgba(65, 108, 114, 0.05)'};\n }\n`;\n\nexport const StyledButtonWrapper = styled.div`\n display: flex;\n margin-top: 2.5rem;\n justify-content: space-between;\n\n @media all and (${device.laptopL}) {\n gap: 10px;\n }\n`;\n\nexport const StyledSaveAddAnother = styled(Button)`\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n padding: 12px 20px;\n gap: 10px;\n width: 176px;\n height: 41px;\n background: rgba(51, 54, 60, 0.2);\n border-radius: 7px;\n flex: none;\n order: 0;\n flex-grow: 0;\n border: none;\n\n &:hover,\n :focus {\n color: black;\n background: rgba(51, 54, 60, 0.2);\n }\n\n @media all and (${device.laptopL}) {\n padding: 6px 10px;\n }\n`;\n\nexport const StyledSave = styled(Button)`\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n padding: 12px 20px;\n gap: 10px;\n width: 70px;\n height: 41px;\n background: #1667e7;\n border-radius: 7px;\n flex: none;\n order: 1;\n flex-grow: 0;\n color: #ffffff;\n border: none;\n\n &:hover,\n :focus {\n color: #ffffff;\n background: #1667e7;\n }\n\n @media all and (${device.laptopL}) {\n padding: 6px 10px;\n }\n`;\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { getToken } from 'services/auth.service';\nimport {\n StyledModal,\n StyledCloseButton,\n StyledTitle,\n StyledNameTextfieldWrapper,\n StyledNameTextfield,\n StyledValueTextfieldWrapper,\n StyledValueTextarea,\n StyledButtonWrapper,\n StyledSaveAddAnother,\n StyledSave,\n ModalHeader,\n} from './GlobalVarsAddModal.style';\n\nconst GlobalVarsAddModal = ({\n visible,\n onCloseModal,\n addGlobalVars,\n globalVarData,\n editGlobalVars,\n}) => {\n const [nameValue, setNameValue] = useState(globalVarData?.name || '');\n const [globalVarsValue, setGlobalVarsValue] = useState(\n globalVarData?.value || ''\n );\n const [isEmptyNameField, setIsEmptyNameField] = useState(false);\n const [isEmptyGlobalVarsField, setIsEmptyGlobalVarsField] = useState(false);\n const token = getToken();\n\n useEffect(() => {\n if (globalVarData?.name) {\n setNameValue(globalVarData?.name);\n setGlobalVarsValue(globalVarData?.value);\n }\n }, [globalVarData?.name, globalVarData?.value]);\n\n const handleChangeName = event => {\n setNameValue(event.target.value);\n if (event.target.value.length > 0) {\n setIsEmptyNameField(false);\n }\n };\n\n const handleChangeGlobalVars = event => {\n setGlobalVarsValue(event.target.value);\n if (event.target.value.length > 0) {\n setIsEmptyGlobalVarsField(false);\n }\n };\n\n const clickSaveAddAnother = async () => {\n if (nameValue !== '' && globalVarsValue !== '') {\n await addGlobalVars(nameValue, globalVarsValue, token);\n setNameValue('');\n setGlobalVarsValue('');\n setIsEmptyNameField(false);\n setIsEmptyGlobalVarsField(false);\n } else {\n setIsEmptyNameField(true);\n setIsEmptyGlobalVarsField(true);\n }\n };\n\n const clickSave = async () => {\n if (nameValue !== '' && globalVarsValue !== '') {\n if (isEditing) {\n await editGlobalVars(nameValue, globalVarsValue, token);\n } else {\n await addGlobalVars(nameValue, globalVarsValue, token);\n }\n setGlobalVarsValue('');\n setIsEmptyNameField(false);\n setIsEmptyGlobalVarsField(false);\n onCloseModal();\n } else {\n setIsEmptyNameField(true);\n setIsEmptyGlobalVarsField(true);\n }\n };\n\n const isEditing = useMemo(() => globalVarData?.name, [globalVarData?.name]);\n\n return (\n <>\n \n {!isEditing ? (\n \n Save and Add Another\n \n ) : null}\n Save \n \n }\n >\n \n {isEditing ? (\n \n Edit Global Vars \n
\n ) : (\n \n Add Global Vars \n
\n )}\n \n \n \n \n Name \n \n \n \n Value \n \n \n
\n \n >\n );\n};\nexport default GlobalVarsAddModal;\n","import styled from 'styled-components';\nimport { Table } from 'antd';\n\nexport const StyledWrapper = styled.div`\n display: flex;\n flex-direction: column;\n padding: 0;\n margin: 0;\n`;\n\nexport const StyledTable = styled(Table)`\n box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.02);\n\n & .ant-table-tbody > tr > td {\n padding: 18px;\n }\n\n & tr > td {\n border-right: 1px solid #33363c1a;\n padding: 12px !important;\n }\n`;\n\nexport const StyledButtonContainer = styled.div`\n display: flex;\n align-items: center;\n & button {\n margin-right: 15px;\n }\n`;\n","import React, { useContext, useState } from 'react';\nimport Button from 'components/Button';\nimport GlobalVarsAddModal from '../GlobalVarsAddModal/GlobalVarsAddModal';\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport { GET_ALL_GLOBAL_VARS } from 'store/action';\nimport {\n StyledTable,\n StyledWrapper,\n StyledButtonContainer,\n} from './GlobalVarsTable.style';\n\nconst GlobalVarsTable = ({\n totalDatas,\n loading,\n pageSize,\n setInitLoading,\n datatable,\n setCurrentData,\n}) => {\n const [, dispatch] = useContext(Context);\n const [currentSize, setCurrentSize] = useState(pageSize);\n const [isModalOpen, setIsModalOpen] = useState(false);\n const [selectedGlobalVar, setSelectedGlobalVar] = useState({\n name: '',\n value: {},\n });\n\n const handlePagination = (current, pageSize) => {\n setInitLoading(true);\n current * pageSize < totalDatas\n ? setCurrentSize(current * pageSize)\n : setCurrentSize(totalDatas);\n datatable.slice((current - 1) * pageSize, current * pageSize);\n setInitLoading(false);\n };\n\n const handleUpdateVersion = row => {\n setIsModalOpen(true);\n setSelectedGlobalVar(prevState => ({\n ...prevState,\n name: row.name,\n value: row.value,\n }));\n };\n\n const handleCloseModal = () => setIsModalOpen(false);\n // const rowSelection = {\n // onChange: (selectedRowKeys, selectedRows) => {\n // console.log(\n // `selectedRowKeys: ${selectedRowKeys}`,\n // 'selectedRows: ',\n // selectedRows\n // );\n // },\n // onSelect: (record, selected, selectedRows) => {\n // console.log(record, selected, selectedRows);\n // },\n // onSelectAll: (selected, selectedRows, changeRows) => {\n // console.log(selected, selectedRows, changeRows);\n // },\n // getCheckboxProps: record => ({\n // name: record.name,\n // }),\n // };\n\n const editGlobalVars = async (name, value, token) => {\n try {\n const res = await apiService.addGlobalVars(name, value, token);\n if (res.data.success) {\n const refetchGlobalVars = async () => {\n setInitLoading(true);\n const allGlobalVars = await apiService.getGlobalVars({}, token);\n const allGlobalVarsData = allGlobalVars.data.results;\n\n await dispatch({\n type: GET_ALL_GLOBAL_VARS,\n payload: allGlobalVarsData,\n });\n setCurrentData(allGlobalVarsData);\n };\n\n refetchGlobalVars();\n setInitLoading(false);\n }\n } catch (err) {\n throw err;\n }\n };\n\n return (\n \n (\n \n handleUpdateVersion(row)}\n />\n \n ),\n },\n ]}\n dataSource={datatable}\n pagination={{\n onChange: (page, pageSize) => {\n handlePagination(page, pageSize);\n },\n pageSize: pageSize,\n total: totalDatas,\n position: ['topRight'],\n }}\n footer={() =>\n `Showing ${\n totalDatas >= currentSize ? currentSize : 1\n } of ${totalDatas} Global Vars.`\n }\n />\n\n \n \n );\n};\n\nexport default GlobalVarsTable;\n","import styled from 'styled-components';\nimport { Input } from 'antd';\nconst { Search } = Input;\n\nexport const StyledSearch = styled(Search)`\n\n > .ant-input-search\n .ant-input-group\n .ant-input-affix-wrapper:not(:last-child) {\n border-top-left-radius: 12px;\n border-bottom-left-radius: 12px;\n }\n\n &\n .ant-input-search\n > .ant-input-group\n > .ant-input-group-addon:last-child\n .ant-input-search-button {\n border-radius: 0 12px 12px 0;\n }\n }\n`;\n","import React from 'react';\nimport { StyledSearch } from './GlobalVarsSearch.style';\n\nconst GlobalVarsSearch = ({ onChange }) => (\n \n);\n\nexport default GlobalVarsSearch;\n","import styled from 'styled-components';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\nimport { Button as AntdButton } from 'antd';\nimport { device } from 'constants/screens';\n\nexport const StyledWrapper = styled.div`\n display: flex;\n`;\n\nexport const StyledContentWrapper = styled.div`\n display: flex;\n flex-direction: column;\n padding: 3%;\n width: 100%;\n overflow-y: auto;\n height: 100vh;\n\n &::-webkit-scrollbar {\n width: 10px;\n margin-right: 15px;\n }\n\n &::-webkit-scrollbar-thumb {\n background: #cccccc;\n border-radius: 10px;\n margin-right: 15px;\n border: 2px solid #f9f9f9;\n }\n`;\n\nexport const StyledNavigationWrapper = styled.div`\n display: flex;\n gap: 3rem;\n padding: 0rem 0rem 0.5rem 0rem;\n width: 100%;\n\n @media all and (${device.laptopL}) {\n padding: 0rem 0rem 0.5rem 0rem;\n }\n`;\n\nexport const StyledButton = styled(Button)`\n background-color: ${cssVariables.primaryBlueHover};\n color: ${cssVariables.primaryBlue};\n width: 97px;\n\n :hover {\n background-color: ${cssVariables.primaryBlue};\n color: ${cssVariables.primaryBlueHover};\n width: 97px;\n }\n\n @media all and (${device.laptopL}) {\n width: 87px;\n }\n`;\n\nexport const StyledTitleWrapper = styled.div`\n width: 100%;\n\n @media all and (${device.laptopL}) {\n font-size: 14px;\n }\n`;\n\nexport const StyledTitle = styled.h2`\n font-weight: 600;\n margin: auto;\n`;\n\nexport const StyledSubTitleWrapper = styled.div`\n font-size: 15px;\n width: 100%;\n\n @media all and (${device.laptopL}) {\n font-size: 13px;\n }\n`;\n\nexport const StyledSubTitle = styled.h3`\n margin: auto;\n`;\n\nexport const StyledSortAddWrapper = styled.div`\n display: flex;\n justify-content: space-bertween;\n width: 100%;\n gap: 10rem;\n padding: 20px 0;\n`;\n\nexport const StyledSortingWrapper = styled.div`\n display: flex;\n width: 80%;\n gap: 2rem;\n`;\n\nexport const StyledSearchWrapper = styled.div``;\n\nexport const StyledDropdownWrapper = styled.div`\n display: flex;\n flex-direction: column;\n width: 25%;\n\n @media all and (${device.laptopL}) {\n width: 30%;\n }\n`;\n\nexport const StyledCountWrapper = styled.div`\n width: 100%;\n height: 30%;\n font-size: 14px;\n line-height: 20px;\n color: rgba(31, 49, 51, 0.74);\n\n @media all and (${device.laptopL}) {\n font-size: 12px;\n }\n`;\n\nexport const StyledAddButtonWrapper = styled.div`\n display: flex;\n`;\n\nexport const StyledAddButton = styled(AntdButton)`\n font-style: normal;\n font-size: 18px;\n text-align: center;\n background: #00bd57;\n border-radius: 4px;\n color: #ffffff;\n border: none;\n margin: auto;\n padding: 6px;\n height: auto;\n\n :hover,\n :focus {\n background: #00bd57;\n color: #ffffff;\n }\n\n @media all and (${device.laptopL}) {\n height: auto;\n }\n`;\n","import React, { useContext, useEffect, useState } from 'react';\nimport { PlusCircleOutlined } from '@ant-design/icons';\nimport { apiService } from 'services/api.service';\nimport { Context } from 'store/store';\nimport { GET_ALL_GLOBAL_VARS } from 'store/action';\nimport AdminSidebar from '../AdminSidebar';\nimport GlobalVarsTable from './GlobalVarsTable/GlobalVarsTable';\nimport GlobalVarsSearch from './GlobalVarsSearch/GlobalVarsSearch';\nimport GlobalVarsAddModal from './GlobalVarsAddModal/GlobalVarsAddModal';\nimport {\n StyledWrapper,\n StyledContentWrapper,\n StyledTitleWrapper,\n StyledTitle,\n StyledSortAddWrapper,\n StyledSortingWrapper,\n StyledSearchWrapper,\n StyledAddButtonWrapper,\n StyledAddButton,\n} from './GlobalVars.style';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\nimport { SidebarRoutes } from '../SidebarRoutes';\nimport { Col, Row } from 'antd';\n\nconst GlobalVars = () => {\n const [state, dispatch] = useContext(Context);\n const token = useSelector(getTokenSelector);\n const {\n admin: { all_global_vars },\n } = state;\n\n const [initLoading, setInitLoading] = useState(true);\n const [openModal, setOpenModal] = useState(false);\n const [currentData, setCurrentData] = useState([]);\n\n useEffect(() => {\n const getGlobalVars = async () => {\n setInitLoading(true);\n try {\n const allGlobalVars = await apiService.getGlobalVars({}, token);\n if (!allGlobalVars || !allGlobalVars.data) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n const allGlobalVarsData = allGlobalVars.data.results;\n\n await dispatch({\n type: GET_ALL_GLOBAL_VARS,\n payload: allGlobalVarsData,\n });\n } catch (error) {\n throw new Error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n getGlobalVars();\n setInitLoading(false);\n }, [dispatch, token]);\n\n useEffect(() => {\n if (all_global_vars.length > 0) {\n setCurrentData(all_global_vars);\n }\n }, [all_global_vars]);\n\n const addGlobalVars = async (name, value, token) => {\n try {\n const res = await apiService.addGlobalVars(name, value, token);\n if (res.data.success) {\n setCurrentData([]);\n const refetchGlobalVars = async () => {\n setInitLoading(true);\n const allGlobalVars = await apiService.getGlobalVars({}, token);\n const allGlobalVarsData = allGlobalVars.data.results;\n\n await dispatch({\n type: GET_ALL_GLOBAL_VARS,\n payload: allGlobalVarsData,\n });\n setCurrentData(allGlobalVarsData);\n };\n\n refetchGlobalVars();\n setInitLoading(false);\n }\n } catch (err) {\n throw err;\n }\n };\n\n const handleCloseModal = () => {\n setOpenModal(false);\n };\n const handleOpenModal = () => {\n setOpenModal(true);\n };\n\n const handleSearch = async event => {\n const searchedValue = all_global_vars.filter(all_global_var =>\n all_global_var.name\n .toLowerCase()\n .includes(event.target.value.toLowerCase())\n );\n\n setCurrentData(searchedValue);\n };\n\n return (\n <>\n \n \n \n \n Global Vars \n \n \n \n \n \n \n {/* COMMENT DO NOT HAVE FUNCTION YET */}\n {/* \n \n \n 0 of {totalDatas} selected.
\n \n */}\n \n \n \n Add Global Vars \n \n \n
\n \n \n \n \n >\n );\n};\n\nexport default GlobalVars;\n","import styled from 'styled-components';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\nimport { device } from 'constants/screens';\n\nexport const StyledWrapper = styled.div`\n display: flex;\n width: 100%;\n`;\n\nexport const StyledContentWrapper = styled.div`\n display: flex;\n flex-direction: column;\n padding: 32px 34px;\n width: 100%;\n overflow-y: scroll;\n\n @media all and (${device.laptopL}) {\n padding: 32px 34px;\n width: 100%;\n overflow-y: scroll;\n }\n\n overflow-y: auto;\n height: 100vh;\n\n &::-webkit-scrollbar {\n width: 10px;\n margin-right: 15px;\n }\n\n &::-webkit-scrollbar-thumb {\n background: #cccccc;\n border-radius: 10px;\n margin-right: 15px;\n border: 2px solid #f9f9f9;\n }\n`;\n\nexport const StyledNavigationWrapper = styled.div`\n display: flex;\n gap: 3rem;\n padding: 0rem 0rem 0.5rem 0rem;\n width: 100%;\n\n @media all and (${device.laptopL}) {\n padding: 0rem 0rem 0.5rem 0rem;\n }\n`;\n\nexport const StyledButton = styled(Button)`\n background-color: ${cssVariables.primaryBlueHover};\n color: ${cssVariables.primaryBlue};\n width: 97px;\n\n :hover {\n background-color: ${cssVariables.primaryBlue};\n color: ${cssVariables.primaryBlueHover};\n width: 97px;\n }\n\n @media all and (${device.laptopL}) {\n width: 87px;\n }\n`;\n\nexport const StyledTitleWrapper = styled.div`\n width: 100%;\n\n @media all and (${device.laptopL}) {\n font-size: 14px;\n }\n`;\n\nexport const StyledTitle = styled.h2`\n font-weight: 600;\n margin: auto;\n`;\n\nexport const StyledSubTitleWrapper = styled.div`\n font-size: 15px;\n width: 100%;\n\n @media all and (${device.laptopL}) {\n font-size: 13px;\n }\n`;\n\nexport const StyledSubTitle = styled.h3`\n margin: auto;\n`;\n\nexport const StyledSortAddWrapper = styled.div`\n display: flex;\n justify-content: space-bertween;\n width: 100%;\n gap: 10rem;\n padding: 20px 0;\n`;\n\nexport const StyledSortingWrapper = styled.div`\n display: flex;\n width: 80%;\n gap: 2rem;\n`;\n\nexport const StyledSearchWrapper = styled.div`\n display: flex;\n width: 500px;\n`;\n\nexport const StyledDropdownWrapper = styled.div`\n display: flex;\n flex-direction: column;\n width: 25%;\n\n @media all and (${device.laptopL}) {\n width: 30%;\n }\n`;\n\nexport const StyledCountWrapper = styled.div`\n width: 100%;\n height: 30%;\n font-size: 14px;\n line-height: 20px;\n color: rgba(31, 49, 51, 0.74);\n\n @media all and (${device.laptopL}) {\n font-size: 12px;\n }\n`;\n","import styled from 'styled-components';\nimport { Table } from 'antd';\n\nexport const StyledWrapper = styled.div`\n display: flex;\n flex-direction: column;\n padding: 0;\n margin: 0;\n`;\n\nexport const StyledTable = styled(Table)`\n box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.02);\n\n & .ant-table-tbody > tr > td {\n padding: 18px;\n }\n\n & tr > td {\n border-right: 1px solid #33363c1a;\n padding: 12px !important;\n }\n`;\n\nexport const StyledButtonContainer = styled.div`\n display: flex;\n align-items: center;\n & button {\n margin-right: 15px;\n }\n`;\n","import styled from 'styled-components';\nimport { Button, Input, Modal } from 'antd';\nimport { cssVariables } from 'styles/root';\nexport const StyledModal = styled(Modal)`\n .dynamic-delete-button {\n position: relative;\n top: 4px;\n margin: 0 8px;\n color: #999;\n font-size: 24px;\n cursor: pointer;\n transition: all 0.3s;\n }\n .dynamic-delete-button:hover {\n color: #777;\n }\n .dynamic-delete-button[disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n }\n [data-theme='dark'] .dynamic-delete-button {\n color: rgba(255, 255, 255, 0.45);\n }\n [data-theme='dark'] .dynamic-delete-button:hover {\n color: rgba(255, 255, 255, 0.65);\n }\n .ant-input-group {\n width: auto;\n }\n`;\n\nexport const StyledFeatureContainer = styled.div`\n font-size: 14px;\n line-height: 20px;\n display: flex;\n flex-direction: column;\n font-weight: ${cssVariables.font.bold};\n padding: 15px 0;\n\n > span:first-child {\n margin-top: 10px;\n }\n\n > input[type='checkbox'] {\n justify-content: space-between;\n align-items: center;\n font-weight: ${cssVariables.font.normal};\n font-size: 12px;\n }\n\n > .ant-checkbox-wrapper + .ant-checkbox-wrapper {\n margin-left: 0;\n }\n`;\n\nexport const StyledSwitchWrapper = styled.div`\n display: flex;\n width: auto;\n flex-direction: column;\n margin-top: 20px;\n\n > div {\n width: 100%;\n display: flex;\n\n b:first-child,\n div:first-child:not(.ant-switch-handle) {\n width: 50%;\n }\n }\n\n > * + * {\n margin: 10px 0;\n }\n`;\nexport const StyledErrorBottomText = styled.span`\n margin-top: 10px;\n font-size: 12px;\n font-weight: ${cssVariables.font.bold};\n text-transform: uppercase;\n text-align: left;\n color: red;\n`;\n","import { PlusOutlined } from '@ant-design/icons';\nimport { Checkbox, Switch } from 'antd';\nimport { Context } from 'store/store';\nimport Input from 'components/Input';\nimport PropTypes from 'prop-types';\nimport { useContext, useEffect } from 'react';\nimport { useState } from 'react';\nimport {\n StyledErrorBottomText,\n StyledFeatureContainer,\n StyledModal,\n StyledSwitchWrapper,\n} from './VersionModal.style';\nimport Button from 'components/Button';\n\nconst VersionModal = ({\n showVersionsModal,\n title,\n setShowVersionsModal,\n selectedVersion,\n addVersion,\n updateVersion,\n datatable,\n}) => {\n const [state, dispatch] = useContext(Context);\n const [versionDetails, setVersionDetails] = useState({});\n const [checkedFeatures, setCheckedFeatures] = useState([]);\n const [showFeaturesInputField, setShowFeaturesInputField] = useState(false);\n const [addedFeature, setAddedFeature] = useState(null);\n const [featureExist, setFeatureExist] = useState(false);\n const [versionExist, setVersionExist] = useState(true);\n const [updateAction, setUpdateAction] = useState(false);\n const [modalTitle, setModalTitle] = useState(title);\n const {\n admin: { all_versions },\n } = state;\n const NEW_BTN_LABEL = 'New Feature';\n const ADD_BTN_LABEL = 'Add';\n const handleChangeField = (name, value) => {\n const newFeaturesList = [];\n switch (name) {\n case 'addedFeature':\n setVersionDetails({\n ...versionDetails,\n features: [...versionDetails.features, { label: value, value }],\n });\n setCheckedFeatures([...checkedFeatures, value]);\n break;\n\n case 'features':\n value.map(checked =>\n newFeaturesList.push({ label: checked, value: checked })\n );\n setVersionDetails({\n ...versionDetails,\n features: newFeaturesList,\n });\n break;\n\n case 'version':\n setVersionDetails({\n ...versionDetails,\n [name]: value,\n });\n if (datatable.find(data => data.version === value)) {\n setVersionExist(true);\n } else {\n setVersionExist(false);\n }\n break;\n default:\n setVersionDetails({\n ...versionDetails,\n [name]: value,\n });\n break;\n }\n\n setShowFeaturesInputField(false);\n setAddedFeature(null);\n setFeatureExist(false);\n };\n\n const handleFeatureCheckboxChange = e => {\n if (!e.target.checked) {\n setCheckedFeatures(\n checkedFeatures.filter(value => value !== e.target.value)\n );\n } else {\n setCheckedFeatures(features => {\n handleChangeField('features', [...features, e.target.value]);\n return [...features, e.target.value];\n });\n }\n };\n\n const handleFeatureInputChange = e => {\n if (checkedFeatures.includes(e.target.value)) {\n setFeatureExist(true);\n } else {\n setFeatureExist(false);\n }\n setAddedFeature(e.target.value);\n };\n\n const handleSave = async () => {\n const finalFeatures = checkedFeatures.reduce(\n (value, field) => ({ ...value, [field]: true }),\n {}\n );\n await addVersion({\n ver: versionDetails.version,\n features: finalFeatures,\n set_current: versionDetails.current,\n });\n setShowVersionsModal(false);\n };\n\n const handleUpdate = async () => {\n let versions = {};\n let updatedFeatures = {};\n datatable.map(res => {\n versions = {\n ...versions,\n [res.version]: res.features,\n };\n });\n versionDetails?.features.map(res => {\n updatedFeatures = {\n ...updatedFeatures,\n [res.value]: checkedFeatures.includes(res.value),\n };\n });\n versions = {\n ...versions,\n [versionDetails.version]: updatedFeatures,\n };\n const params = {\n current:\n versionDetails.current === true\n ? versionDetails.version\n : all_versions.find(versionList => versionList.status === 'current')\n .version,\n versions,\n };\n await updateVersion(JSON.stringify(params));\n setShowVersionsModal(false);\n };\n\n useEffect(() => {\n let featuresOptions = [];\n const features = selectedVersion?.features\n ? Object.entries(selectedVersion?.features).map(version => {\n if (version[1]) {\n featuresOptions.push(version[0]);\n }\n return {\n value: version[0],\n label: version[0],\n };\n })\n : null;\n setCheckedFeatures(featuresOptions);\n setVersionDetails({\n ...versionDetails,\n version: selectedVersion?.version,\n features,\n current: title !== 'Update Version',\n });\n }, [showVersionsModal, selectedVersion, updateAction]);\n\n useEffect(() => {\n setModalTitle(title);\n if (title === 'Update Version') {\n setUpdateAction(true);\n } else {\n setUpdateAction(false);\n }\n }, [title, showVersionsModal]);\n\n const handleCloneVersion = () => {\n setUpdateAction(false);\n setModalTitle('Add Version');\n };\n\n return (\n setShowVersionsModal(false)}\n footer={[\n setShowVersionsModal(false)}\n value={'Cancel'}\n key=\"cancel\"\n />,\n updateAction && (\n \n ),\n (updateAction ? handleUpdate() : handleSave())}\n disabled={!updateAction && versionExist}\n value={'Submit'}\n key=\"submit\"\n />,\n ]}\n >\n handleChangeField('version', e.target.value)}\n disabled={updateAction}\n />\n \n Features \n {versionDetails?.features?.map((features, key) => (\n \n {features.label}\n \n ))}\n \n {showFeaturesInputField && (\n <>\n handleFeatureInputChange(e)}\n addonAfter={\n handleChangeField('addedFeature', addedFeature)}\n />\n }\n />\n {featureExist && (\n Feature exist. \n )}\n >\n )}\n {!showFeaturesInputField && !updateAction && (\n setShowFeaturesInputField(true)}\n startIcon={ }\n />\n )}\n \n \n {'Current Version'} \n handleChangeField('current', value)}\n key={versionDetails.version}\n />\n
\n \n \n );\n};\n\nVersionModal.propTypes = {\n showVersionsModal: PropTypes.bool.isRequired,\n setShoVersionsModal: PropTypes.func.isRequired,\n selectedVersion: PropTypes.object,\n addVersion: PropTypes.func,\n updateVersion: PropTypes.func,\n};\n\nexport default VersionModal;\n","import React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { apiService } from 'services/api.service';\nimport {\n StyledButtonContainer,\n StyledTable,\n StyledWrapper,\n} from './VersionsTable.style';\nimport Button from 'components/Button';\nimport VersionModal from '../VersionsModal/VersionModal';\nimport { useEffect } from 'react';\nimport { Col, Row } from 'antd';\n\nconst VersionsTable = ({\n totalDatas,\n loading,\n pageSize,\n setInitLoading,\n datatable,\n getAllVersions,\n sentinel,\n token,\n setCurrentData,\n}) => {\n const [currentSize, setCurrentSize] = useState(pageSize);\n const [showVersionsModal, setShowVersionsModal] = useState(false);\n const [modalTitle, setModalTitle] = useState('');\n const [currentVersion, setCurrentVersion] = useState([]);\n const [selectedVersion, setSelectedVersion] = useState([]);\n\n const handlePagination = (current, pageSize) => {\n setInitLoading(true);\n current * pageSize < totalDatas\n ? setCurrentSize(current * pageSize)\n : setCurrentSize(totalDatas);\n datatable.slice((current - 1) * pageSize, current * pageSize);\n setInitLoading(false);\n };\n\n const handleAddVersions = () => {\n setModalTitle('Add Version');\n setShowVersionsModal(true);\n };\n\n const handleUpdateVersion = versionSelected => {\n setSelectedVersion(\n datatable.find(\n versionDetails => versionDetails.version === versionSelected\n )\n );\n setModalTitle('Update Version');\n setShowVersionsModal(true);\n };\n\n const addVersion = async versionDetails => {\n try {\n const res = await apiService.addVersions(sentinel, token, versionDetails);\n if (res.data.success) {\n setCurrentData([]);\n const refetchVersions = async () => {\n setInitLoading(true);\n await getAllVersions();\n };\n refetchVersions();\n setInitLoading(false);\n }\n } catch (err) {\n throw err;\n }\n };\n\n const updateVersion = async version => {\n try {\n const res = await apiService.addGlobalVars('features', version, token);\n if (res.data.success) {\n setCurrentData([]);\n const refetchVersions = async () => {\n setInitLoading(true);\n await getAllVersions();\n };\n refetchVersions();\n setInitLoading(false);\n }\n } catch (err) {\n throw err;\n }\n };\n\n useEffect(() => {\n const currentVersion = datatable.find(\n version => version.status === 'current'\n );\n setCurrentVersion(currentVersion);\n }, [datatable]);\n return (\n \n a.current.localeCompare(b.current),\n },\n {\n title: 'Actions',\n dataIndex: 'status',\n render: (value, row) => (\n \n {value === 'current' && (\n \n \n \n )}\n \n handleUpdateVersion(row.version)}\n full\n />\n \n
\n ),\n },\n ]}\n dataSource={datatable}\n pagination={{\n onChange: (page, pageSize) => {\n handlePagination(page, pageSize);\n },\n pageSize: pageSize,\n total: totalDatas,\n position: ['topRight'],\n }}\n footer={() =>\n `Showing ${\n totalDatas >= currentSize ? currentSize : 1\n } of ${totalDatas} Versions.`\n }\n showSorterTooltip={{\n title: 'Click to sort',\n placement: 'topRight',\n }}\n />\n \n \n );\n};\n\nVersionsTable.propTypes = {\n loading: PropTypes.bool.isRequired,\n setInitLoading: PropTypes.bool,\n getAllVersions: PropTypes.func.isRequired,\n};\n\nexport default VersionsTable;\n","import styled from 'styled-components';\nimport { Input } from 'antd';\nconst { Search } = Input;\n\nexport const StyledSearch = styled(Search)`\n\n > .ant-input-search\n .ant-input-group\n .ant-input-affix-wrapper:not(:last-child) {\n border-top-left-radius: 12px;\n border-bottom-left-radius: 12px;\n }\n\n &\n .ant-input-search\n > .ant-input-group\n > .ant-input-group-addon:last-child\n .ant-input-search-button {\n border-radius: 0 12px 12px 0;\n }\n }\n`;\n","import React from 'react';\nimport { StyledSearch } from './VersionSearch.style';\n\nconst VersionSearch = ({ onChange }) => (\n \n);\n\nexport default VersionSearch;\n","import React, { useContext, useEffect, useState } from 'react';\nimport { apiService } from 'services/api.service';\nimport { Context } from 'store/store';\nimport { GET_ALL_VERSIONS } from 'store/action';\nimport AdminSidebar from '../AdminSidebar';\nimport {\n StyledWrapper,\n StyledContentWrapper,\n StyledTitleWrapper,\n StyledTitle,\n StyledSortAddWrapper,\n StyledSortingWrapper,\n StyledSearchWrapper,\n} from './Versions.style';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { getTokenSelector } from 'selectors/user';\nimport VersionsTable from './VersionsTable/VersionsTable';\nimport VersionSearch from './VersionSearch/VersionSearch';\nimport _ from 'lodash';\nimport { SidebarRoutes } from '../SidebarRoutes';\n\nconst Versions = () => {\n const [state, dispatch] = useContext(Context);\n const token = getTokenSelector(state);\n const [initLoading, setInitLoading] = useState(true);\n const [currentData, setCurrentData] = useState([]);\n const {\n admin: { all_versions },\n sentinel,\n } = state;\n\n useEffect(() => {\n const getVersions = async () => {\n setInitLoading(true);\n try {\n await getAllVersions();\n } catch (error) {\n throw new Error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n getVersions();\n setInitLoading(false);\n }, [dispatch, token]);\n\n useEffect(() => {\n if (all_versions?.length > 0) {\n setCurrentData(_.orderBy(all_versions, 'status', 'desc'));\n }\n }, [all_versions]);\n\n const getAllVersions = async () => {\n const allVersions = await apiService.getVersions(sentinel, token);\n if (!allVersions || !allVersions.data) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n\n const versionData = allVersions?.data.report[0];\n const allVersionsData = Object.entries(versionData.versions).map(\n version => ({\n version: version[0],\n features_string: JSON.stringify(version[1]),\n features: version[1],\n status: version[0] === versionData.current ? 'current' : '',\n })\n );\n await dispatch({\n type: GET_ALL_VERSIONS,\n payload: allVersionsData,\n });\n };\n\n const handleSearch = async event => {\n const searchedValue = all_versions.filter(all_version =>\n all_version.version\n .toLowerCase()\n .includes(event.target.value.toLowerCase())\n );\n setCurrentData(searchedValue);\n };\n\n return (\n <>\n \n \n \n \n Version Management \n \n \n \n \n \n \n \n \n \n \n \n >\n );\n};\n\nexport default Versions;\n","import Users from './Users';\nimport ROUTES from 'constants/routes';\nimport GlobalVars from './GlobalVars';\nimport ActivityLogs from 'components/ActivityLogs';\nimport Versions from './Versions';\nimport {\n CopyOutlined,\n FieldTimeOutlined,\n GlobalOutlined,\n UserOutlined,\n} from '@ant-design/icons';\n\nexport const SidebarRoutes = [\n {\n name: 'Users List',\n path: ROUTES.ADMIN_USERS,\n component: Users,\n key: 'admin_users',\n icon: ,\n },\n {\n name: 'Global Vars',\n path: ROUTES.ADMIN_GLOBAL_VARS,\n component: GlobalVars,\n key: 'global_vars',\n icon: ,\n },\n {\n name: 'All Users Activity Logs',\n path: ROUTES.ADMIN_ACTIVITY_LOGS,\n component: ActivityLogs,\n key: 'activity_logs',\n icon: ,\n },\n {\n name: 'Version Management',\n path: ROUTES.ADMIN_VERSIONS,\n component: Versions,\n key: 'versions',\n icon: ,\n },\n];\n","import React from 'react';\nimport {\n Timeline,\n Empty,\n Spin,\n Dropdown,\n Menu,\n Row,\n Col,\n Typography,\n Grid,\n} from 'antd';\nimport moment from 'moment';\nimport {\n CaretRightFilled,\n DownloadOutlined,\n DownOutlined,\n UpOutlined,\n} from '@ant-design/icons';\nimport InfiniteScroll from 'react-infinite-scroll-component';\n\nimport {\n StyledWrapper,\n StyledContentWrapper,\n StyledButtonContainer,\n StyledTitleWrapper,\n StyledContentSubHeaderWrapper,\n StyledLogsDetailsContainer,\n StyledUserInfoContainer,\n StyledDetailsContainer,\n StyledSortSelected,\n} from './ActivityLogs.style';\nimport Button from 'components/Button';\nimport useActivityLogs from './hooks';\nimport ActivityLogsModal from './ActivityLogsModal';\nimport AdminSidebar from 'pages/Admin/AdminSidebar';\nimport DateFilter from 'components/DateFilter';\nimport { StyledDatePickerButton } from 'pages/BotDetails/Analytics/Analytics.styles';\n\nimport useSelector from 'store/useSelector';\nimport { userSelector } from 'selectors/user';\nimport Paragraph from 'antd/lib/typography/Paragraph';\nimport { SidebarRoutes } from 'pages/Admin/SidebarRoutes';\n\nconst { Title } = Typography;\nconst { useBreakpoint } = Grid;\n\nconst ActivityLogs = () => {\n const {\n activityLogs,\n dateFilter,\n selectedActivityLogs,\n showDetailsModal,\n isLoading,\n filters,\n activityLogsState,\n sort,\n loadingTemplate,\n endMessageTemplate,\n isAdmin,\n formatDateTime,\n handleDateFilterChange,\n setSelectedActivityLogDetails,\n handleModalClosing,\n handleModalOpening,\n handleDownloadActivityLogs,\n getActivityLogs,\n setSort,\n handleChangeActivityDateSorting,\n fetchNextScroll,\n parseToStartAndEndOfDayISODate,\n activeBots,\n renderNewBotName,\n } = useActivityLogs();\n const screens = useBreakpoint();\n const user = useSelector(userSelector);\n const rangePickerStyle = {\n width: screens.xs ? '100%' : 'auto',\n padding: screens.xs ? '8px 12px' : 'auto',\n };\n const dropdownStyle = {\n width: screens.xs ? '100%' : 'auto',\n };\n\n const RightEl = (\n \n }\n value={'Export'}\n variant=\"success\"\n onClick={handleDownloadActivityLogs}\n disabled={activityLogs?.length === 0}\n style={{ marginLeft: 5 }}\n s\n />\n \n );\n const SortDropdownMenu = (\n \n }\n onClick={() => handleChangeActivityDateSorting('asc')}\n sortType={sort}\n >\n Ascending\n \n }\n onClick={() => handleChangeActivityDateSorting('desc')}\n sortType={sort}\n >\n Descending\n \n \n );\n\n return (\n \n {isAdmin && }\n \n \n \n \n Activity Logs \n \n \n {RightEl}\n \n \n\n \n \n \n Activity logs showing results from\n \n \n \n trigger.parentNode}\n dropdownClassName=\"responsive-range-picker-dropdown\"\n popupStyle={dropdownStyle}\n />\n \n \n {\n const { validStartDate, validEndDate } =\n parseToStartAndEndOfDayISODate(dateFilter);\n getActivityLogs(validStartDate, validEndDate, {\n isExecutedAlready: false,\n sort: 'desc',\n });\n setSort('desc');\n }}\n value={`Go`}\n endIcon={ }\n />\n \n \n \n \n {filters}\n \n \n Sort by:\n \n \n \n \n
\n \n {activityLogs?.length > 0 && (\n \n \n {activityLogsState.items?.map(activityLog => (\n \n \n {activityLog._source.activity_action}\n \n setSelectedActivityLogDetails(activityLog._source)\n }\n />\n \n {isAdmin && activityLog._source.user?.email && (\n \n {activityLog._source.user.email}\n \n )}\n \n ))}\n \n \n )}\n {(activityLogs?.length === 0 || !activityLogs) && (\n No Log Found.}\n style={{ width: '100%' }}\n />\n )}\n \n \n \n\n selectedActivityLogs?.id === activeBot.jid\n )\n ? 'Active Bot'\n : 'Inactive Bot'\n }\n renderNewBotName={renderNewBotName}\n />\n \n );\n};\n\nexport default ActivityLogs;\n","import styled from 'styled-components';\nimport { Card } from 'antd';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledWrapper = styled.div`\n margin: 1% 2%;\n`;\n\nexport const StyledPageTitle = styled.div`\n & h3 {\n display: flex;\n width: 100%;\n font-size: 24px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\nexport const StyledCard = styled(Card)`\n font-size: 32px;\n text-align: center;\n margin-bottom: 16px;\n background-color: ${props => (props.isGray ? '#cccccc' : 'none')};\n border-radius: 4px;\n\n > span {\n font-size: 16px;\n }\n`;\n\nexport const StyledContent = styled.div`\n display: flex;\n height: auto;\n background: #fff;\n width: 100%;\n`;\n\nexport const ProgressSection = styled.div`\n display: flex;\n align-items: center;\n\n > button {\n margin-left: 16px;\n }\n`;\n\nexport const FlagName = styled.p`\n font-size: 1rem;\n`;\n","import { useContext, useMemo } from 'react';\n\nimport { Context } from 'store/store';\nimport { apiService } from 'services/api.service';\nimport { DELETE_ONBOARDING_FLAG, RESET_ONBOARDING_FLAG } from 'store/action';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst useOnboardingFlags = () => {\n const [state, dispatch] = useContext(Context);\n const token = useSelector(getTokenSelector);\n const {\n sentinel,\n graph,\n plan: { plan_type, onboarding_flag },\n } = state;\n\n const resetFlag = async flag => {\n const result = await apiService.resetOnboardingFlag(\n sentinel,\n token,\n graph,\n flag\n );\n\n if (result) {\n dispatch({ type: DELETE_ONBOARDING_FLAG, payload: flag });\n }\n };\n\n const resetAllFlags = async () => {\n const result = await apiService.resetAllOnboardingFlag(\n sentinel,\n token,\n graph\n );\n\n if (result) {\n dispatch({ type: RESET_ONBOARDING_FLAG });\n }\n };\n\n const isFreeUser = useMemo(() => plan_type === 'free', [plan_type]);\n\n return {\n onboarding_flag,\n resetFlag,\n resetAllFlags,\n isFreeUser,\n dispatch,\n };\n};\n\nexport default useOnboardingFlags;\n","import React, { useMemo } from 'react';\nimport { Row, Col, Button, Progress, Popover, Typography } from 'antd';\nimport { CheckCircleTwoTone, CloseCircleTwoTone } from '@ant-design/icons';\nimport {\n StyledWrapper,\n StyledPageTitle,\n StyledContent,\n StyledCard,\n ProgressSection,\n FlagName,\n} from './OnboardingFlags.style';\nimport useOnboardingFlags from './hooks';\nimport onboardingFlags from 'constants/onboardingFlags.json';\n\nconst { Title } = Typography;\n\nconst paidUsersExclusiveOnboarding = ['TextIngest', 'ImportAnswerButton'];\n\nconst OnboardingFlags = () => {\n const { onboarding_flag, resetFlag, resetAllFlags, isFreeUser } =\n useOnboardingFlags();\n\n const handleReset = value => {\n resetFlag(value);\n };\n\n const handleResetAll = () => {\n resetAllFlags();\n };\n\n const percent = useMemo(\n () => Math.round((onboarding_flag.length / onboardingFlags.length) * 100),\n [onboarding_flag.length]\n );\n\n return (\n \n \n Onboarding Flags \n \n \n \n Reset All \n \n \n \n {onboardingFlags.map(flag => (\n \n \n \n {onboarding_flag.includes(flag.key) ? (\n \n \n \n \n
\n ) : (\n \n \n {' '}\n \n \n
\n )}\n\n {flag.name} \n\n {onboarding_flag.includes(flag.key) &&\n flag.key !== 'ViewBotButton' ? (\n \n \n \n handleReset(flag.key)}>\n Reset\n \n \n
\n ) : null}\n \n \n \n ))}\n
\n \n \n );\n};\n\nexport default OnboardingFlags;\n","import styled from 'styled-components';\nimport { Modal, Form, Button as AntdButton, Card, Tag, Row, Col } from 'antd';\nimport { FileFilled } from '@ant-design/icons';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\n\nexport const PageHeader = styled.h3`\n font-family: Helvetica;\n font-style: normal;\n font-weight: ${cssVariables.font.bold};\n font-size: 1rem;\n line-height: 37px;\n`;\n\nexport const Divider = styled.div`\n border: 1px solid #d9dadb;\n margin: ${props => (props.space ? props.space : '24')}px 0;\n`;\n\nexport const StyledModal = styled(Modal)`\n font-family: Helvetica;\n`;\n\nexport const StyledButton = styled(Button)`\n width: 100%;\n margin-top: 24px;\n background: #ffffff !important;\n border: 1.5px solid #eff3f4;\n font-size: 14px;\n font-weight: ${cssVariables.font.bold};\n color: #1667e7 !important;\n`;\n\nexport const SuccessMessage = styled.div`\n width: 368px;\n margin: 0 auto;\n text-align: center;\n\n > h4 {\n font-size: 16px;\n font-weight: ${cssVariables.font.bold};\n }\n > p {\n font-size: 14px;\n line-height: 1;\n }\n\n ${StyledButton} {\n background: #1667e7 !important;\n color: #ffffff !important;\n }\n`;\n\nexport const InvoiceStatus = styled(Tag)`\n color: ${props => (props.status === 'paid' ? 'green' : '#808583')};\n`;\n\nexport const CardElementsContainer = styled.div`\n display: flex;\n justify-content: space-between;\n`;\n\nexport const FormWrapper = styled(Form)`\n margin-top: 20px;\n`;\n\nexport const PaymentMethodsList = styled.div`\n display: flex;\n flex-direction: column;\n`;\n\nexport const StyledWrapper = styled.div``;\n\nexport const StyledPageTitle = styled.div`\n & h3 {\n display: flex;\n width: 100%;\n font-size: 24px;\n font-weight: ${cssVariables.font.strong};\n }\n`;\n\nexport const StyledContent = styled(Card)`\n background: #fff;\n margin-bottom: 10px;\n border-radius: 15px;\n\n & .ant-card-head-title {\n padding-bottom: 0;\n }\n & .ant-card-body {\n padding-top: 15px !important;\n }\n & .ant-alert {\n margin: 20px;\n }\n\n & h2 {\n font-size: 1.2rem;\n font-weight: ${cssVariables.font.bold};\n }\n`;\n\nexport const StyledCurrentPlan = styled(Row)`\n // display: flex;\n // align-items: center;\n // justify-content: space-between;\n // width: 80%;\n margin-bottom: 40px;\n`;\n\nexport const StyledPlanInfo = styled.div`\n & span,\n p,\n a {\n font-size: 14px;\n }\n`;\n\nexport const StyledPlanCTA = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-between;\n`;\n\nexport const StyledTransactionParagraph = styled.span`\n color: ${props => (props.isRed ? '#ff4d4f' : 'auto')};\n margin-right: 5px;\n`;\n\nexport const StyledFormButtons = styled.div`\n display: flex;\n column-gap: 10px;\n\n & button {\n width: 100%;\n }\n\n &:first-child {\n margin: 0 !important;\n }\n`;\n\nexport const StyledShowConfirmContent = styled.div`\n margin-left: 36;\n`;\n\nexport const StyledPaymentInfoContainer = styled(Col)`\n // display: flex;\n // flex-direction: column;\n // > * + * {\n // margin: 5px 0;\n // }\n`;\n\nexport const StyledAntdButton = styled(AntdButton)`\n margin-bottom: 10px;\n`;\n\nexport const StyledPaymentInfoDetail = styled.div`\n display: flex;\n justify-content: space-between;\n\n .space-between {\n width: 80%;\n justify-content: space-between;\n }\n`;\n\nexport const StyledInvoiceInfo = styled.div`\n margin-bottom: 40px;\n`;\n\nexport const StyledInvoiceDetail = styled.div`\n display: flex;\n justify-content: space-between;\n margin-top: 10px;\n`;\n\nexport const StyledFileFilled = styled(FileFilled)`\n color: #3993dd;\n margin-right: 8px;\n`;\n\nexport const StyledIsProcessingWrapper = styled.div`\n ${props => {\n if (props.isProcessing) {\n return `\n -webkit-filter: blur(0.8px);\n -moz-filter: blur(0.8px);\n -o-filter: blur(0.8px);\n -ms-filter: blur(0.8px);\n filter: blur(0.8px);\n `;\n }\n }}\n`;\n","import { createSelector } from 'reselect';\n\nexport const userInvoiceSelector = state => state.invoices;\n\nexport const invoicesSelector = createSelector(\n userInvoiceSelector,\n invoices => invoices || []\n);\n","import { useCallback, useContext, useEffect, useState } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { message } from 'antd';\n\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\nimport {\n customerChargesSelector,\n customerIdSelector,\n customerPaymentsSelector,\n maxTransactionCountSelector,\n planDetailsSelector,\n planTypeSelector,\n} from 'selectors/plan';\nimport {\n SET_INVOICES,\n SET_PAYMENT_METHODS,\n SET_USER_SUBSCRIPTION,\n SHOW_CANCEL_SUBSCRIPTION_MODAL,\n} from 'store/action';\nimport ROUTES from 'constants/routes';\nimport { stripeAPIService } from 'services/stripeAPI.service';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport { getTokenSelector } from 'selectors/user';\nimport {\n currentTransactionCountSelector,\n defaultPaymentMethodSelector,\n isCancelledSubscriptionSelector,\n isPaymentInfoEmptySelector,\n isProcessingSelector,\n isTrialSubscriptionSelector,\n paymentErrorSelector,\n paymentMethodsSelector,\n planRenewDateSelector,\n subscriptionIdSelector,\n subscriptionSelector,\n transactionPercentageSelector,\n trialDaysLeftSelector,\n} from 'selectors/subscription';\nimport { billingDetailsSelector } from 'selectors/subscription';\nimport { AUTO_RENEW_SUCCESS_MESSAGE } from 'constants/plan';\nimport { invoicesSelector } from 'selectors/invoice';\n\nconst usePlanAndPayments = () => {\n const { push } = useHistory();\n const [state, dispatch] = useContext(Context);\n const { sentinel, graph, user } = state;\n\n const billingDetails = useSelector(billingDetailsSelector);\n const currentTransactionCount = useSelector(currentTransactionCountSelector);\n const customerCharges = useSelector(customerChargesSelector);\n const customerId = useSelector(customerIdSelector);\n const customerPayments = useSelector(customerPaymentsSelector);\n const defaultPaymentMethod = useSelector(defaultPaymentMethodSelector);\n const paymentError = useSelector(paymentErrorSelector);\n const invoices = useSelector(invoicesSelector);\n const isCancelledSubscription = useSelector(isCancelledSubscriptionSelector);\n const isPaymentInfoEmpty = useSelector(isPaymentInfoEmptySelector);\n const isProcessing = useSelector(isProcessingSelector);\n const isTrialSubscription = useSelector(isTrialSubscriptionSelector);\n const maxTransactionCount = useSelector(maxTransactionCountSelector);\n const paymentMethods = useSelector(paymentMethodsSelector);\n const planDetails = useSelector(planDetailsSelector);\n const planRenewDate = useSelector(planRenewDateSelector);\n const planType = useSelector(planTypeSelector);\n const subscription = useSelector(subscriptionSelector);\n const subscriptionId = useSelector(subscriptionIdSelector);\n const token = useSelector(getTokenSelector);\n const transactionPercentage = useSelector(transactionPercentageSelector);\n const trialDaysLeft = useSelector(trialDaysLeftSelector);\n\n const [userDetails, setUserDetails] = useState({\n name: '',\n email: '',\n });\n const [loading, setLoading] = useState(false);\n const [showAddPaymentMethod, setShowAddPaymentMethod] = useState(false);\n const [isCreatingPaymentMethod, setIsCreatingPaymentMethod] = useState(false);\n const [selectedKey, setSelectedKey] = useState(null);\n const [expandedRows, setExpandedRows] = useState({});\n\n const showAddPaymentMethodForm = () => {\n setShowAddPaymentMethod(true);\n };\n\n const handleChangeCard = async () => {\n const returnUrl = `${window.location.origin}${ROUTES.PLAN_AND_PAYMENTS}`;\n try {\n const res = await stripeAPIService.changeCard(\n returnUrl,\n graph,\n sentinel,\n token\n );\n if (!res.data?.report[0]) {\n throw new Error('No payment data found!');\n } else if (res.data?.report[0]) {\n const checkoutURl = res.data.report[0]?.url;\n if (checkoutURl) {\n window.location.href = checkoutURl;\n } else {\n throw new Error(GET_DATA_ERROR);\n }\n }\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const fetchSubscriptionStatus = async () => {\n setLoading(true);\n try {\n const res = await stripeAPIService.getSubscriptionStatus(\n graph,\n sentinel,\n token\n );\n if (res.data?.report) {\n dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n }\n } catch (error) {\n setLoading(false);\n message.error(error?.message || GET_DATA_ERROR);\n }\n setLoading(false);\n };\n\n const fetchInvoiceList = async () => {\n setLoading(true);\n try {\n const res = await stripeAPIService.getInvoiceList(graph, sentinel, token);\n if (res.data?.report) {\n dispatch({\n type: SET_INVOICES,\n payload: res.data.report[0]?.data,\n });\n }\n } catch (error) {\n setLoading(false);\n message.error(error?.message || GET_DATA_ERROR);\n }\n setLoading(false);\n };\n\n const renewSubscription = async () => {\n setLoading(true);\n try {\n const res = await stripeAPIService.autoRenewSubscription(\n graph,\n sentinel,\n token\n );\n if (res.data?.report[0]) {\n dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n\n message.success(AUTO_RENEW_SUCCESS_MESSAGE);\n }\n } catch (error) {\n message.error(GET_DATA_ERROR);\n }\n setLoading(false);\n };\n\n const getPaymentMethods = async () => {\n setLoading(true);\n try {\n const res = await stripeAPIService.getPaymentMethods(\n graph,\n sentinel,\n token\n );\n if (res.data?.report[0]) {\n const paymentMethod = res.data.report[0]?.data;\n dispatch({\n type: SET_PAYMENT_METHODS,\n payload: paymentMethod,\n });\n }\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n setLoading(false);\n };\n\n const onCancelChangePaymentMethod = () => {\n setShowAddPaymentMethod(false);\n setUserDetails({\n name: '',\n email: user.email,\n });\n setIsCreatingPaymentMethod(false);\n };\n\n const onFormInput = useCallback(\n event => {\n const { name, value } = event.target;\n setUserDetails({\n ...userDetails,\n [name]: value,\n });\n },\n [userDetails]\n );\n\n const showConfirmCancelSubscriptionModal = () => {\n dispatch({\n type: SHOW_CANCEL_SUBSCRIPTION_MODAL,\n });\n };\n\n const formatDateTime = dateTime => {\n return new Date(dateTime).toLocaleString('en-US', {\n day: 'numeric',\n year: 'numeric',\n month: 'long',\n });\n };\n\n useEffect(() => {\n async function fetchData() {\n await getPaymentMethods();\n await fetchSubscriptionStatus();\n await fetchInvoiceList();\n }\n\n if (planType !== 'free' || isTrialSubscription) {\n fetchData();\n }\n }, [planType, isTrialSubscription]);\n\n const handleExpand = (rowKey, isExpanded) => {\n setExpandedRows({\n ...expandedRows,\n [rowKey]: !isExpanded || true,\n });\n };\n\n return {\n billingDetails,\n currentTransactionCount,\n customerCharges,\n customerId,\n customerPayments,\n defaultPaymentMethod,\n paymentError,\n handleChangeCard,\n invoices,\n isCancelledSubscription,\n isCreatingPaymentMethod,\n isPaymentInfoEmpty,\n isProcessing,\n isTrialSubscription,\n loading,\n maxTransactionCount,\n onCancelChangePaymentMethod,\n onFormInput,\n planDetails,\n planRenewDate,\n planType,\n paymentMethods,\n renewSubscription,\n showAddPaymentMethod,\n showAddPaymentMethodForm,\n showConfirmCancelSubscriptionModal,\n subscription,\n subscriptionId,\n transactionPercentage,\n trialDaysLeft,\n userDetails,\n formatDateTime,\n selectedKey,\n setSelectedKey,\n expandedRows,\n handleExpand,\n };\n};\n\nexport default usePlanAndPayments;\n","import React from 'react';\nimport {\n Progress,\n Button as AntdButton,\n Tag,\n Spin,\n Space,\n Typography,\n Descriptions,\n Table,\n Col,\n Menu,\n Row,\n} from 'antd';\nimport { Link } from 'react-router-dom';\n\nimport Button from 'components/Button';\nimport ROUTES from 'constants/routes';\nimport {\n PageHeader,\n InvoiceStatus,\n StyledWrapper,\n StyledPageTitle,\n StyledContent,\n StyledCurrentPlan,\n StyledPlanInfo,\n StyledPlanCTA,\n StyledPaymentInfoContainer,\n StyledPaymentInfoDetail,\n StyledInvoiceInfo,\n StyledTransactionParagraph,\n StyledIsProcessingWrapper,\n} from './PlanAndPayments.style';\nimport { ZSB_CONTACT } from 'constants/outboundLinks';\nimport usePlanAndPayments from './hooks';\nimport FallbackBoundary from 'components/FallbackBoundary';\nimport {\n StyledFlexColumn,\n StyledFlexRowLeft,\n} from 'styles/GenericStyledComponents';\nimport Alert from 'components/Alert';\nimport { IS_PROCESSING_SUBSCRIPTION, PRICE_PER_QUERY } from 'constants/plan';\nimport { cssVariables } from 'styles/root';\nimport AnchorButton from 'components/Button/AnchorButton';\nimport { detailedPlanFeatures } from '../planDetails';\nimport {\n ArrowUpOutlined,\n CalendarOutlined,\n CloseOutlined,\n CreditCardOutlined,\n EditOutlined,\n ExclamationCircleOutlined,\n FilePdfOutlined,\n FileSearchOutlined,\n SelectOutlined,\n} from '@ant-design/icons';\nimport moment from 'moment';\nimport ToolTip from 'components/ToolTips/BaseToolTip';\n\nconst { Paragraph } = Typography;\n\nconst PlanAndPaymentsWrapper = () => {\n const {\n billingDetails,\n currentTransactionCount,\n defaultPaymentMethod,\n paymentError,\n handleChangeCard,\n isCancelledSubscription,\n invoices,\n isPaymentInfoEmpty,\n isProcessing,\n isTrialSubscription,\n loading,\n maxTransactionCount,\n planDetails,\n planRenewDate,\n planType,\n renewSubscription,\n showConfirmCancelSubscriptionModal,\n subscriptionId,\n transactionPercentage,\n trialDaysLeft,\n formatDateTime,\n selectedKey,\n setSelectedKey,\n expandedRows,\n handleExpand,\n } = usePlanAndPayments();\n\n const getTransactionCountParagraph = () => {\n if (currentTransactionCount > maxTransactionCount) {\n const excessTransaction = Math.abs(\n currentTransactionCount - maxTransactionCount\n );\n return `You have gone above your transaction limit this month. Excess transaction count: ${excessTransaction} `;\n } else {\n return `You have used ${currentTransactionCount} of ${maxTransactionCount} ${\n planType === 'free' ? 'free' : ''\n } transactions per month.`;\n }\n };\n\n const columns = [\n {\n title: '',\n dataIndex: 'periodStart',\n key: 'periodStart',\n render: (text, value) => (\n <>\n handleExpand(value?.id, info?.expanded),\n }}\n >\n Invoice from {formatDateTime(text * 1000)} to{' '}\n {formatDateTime(value.periodEnd * 1000)}{' '}\n \n >\n ),\n },\n {\n title: '',\n dataIndex: 'status',\n key: 'status',\n render: text => (\n \n {text?.charAt(0).toUpperCase() + text?.slice(1)}\n \n ),\n },\n {\n title: '',\n dataIndex: 'download',\n key: 'download',\n render: (text, value) => (\n <>\n }\n href={value.hostedURL}\n target=\"_blank\"\n value=\"View\"\n />\n }\n href={value.file}\n target=\"_blank\"\n />\n >\n ),\n },\n ];\n\n return (\n \n \n {'BILLING'} \n \n Current Plan}>\n \n \n \n \n \n {planType?.charAt(0).toUpperCase() + planType?.slice(1)}\n \n {isTrialSubscription ? (\n {'Trial'} \n ) : paymentError ? (\n {'Payment failed'} \n ) : null}\n \n\n \n {!!planType && planType !== 'free' ? (\n \n
\n {detailedPlanFeatures(planDetails[planType][0])} per month\n for {maxTransactionCount} queries,\n {PRICE_PER_QUERY} per query thereafter.{' '}\n \n {planRenewDate && !isCancelledSubscription ? (\n
Your plan renews on {planRenewDate}.
\n ) : null}\n {trialDaysLeft ? (\n
{`Your trial subscription will end in ${trialDaysLeft} day(s)`}
\n ) : null}\n
\n ) : null}\n \n \n \n
\n
80}\n >\n {getTransactionCountParagraph()}\n \n
{'Upgrade for more'}\n
\n \n \n {isProcessing ? (\n \n \n \n ) : null}\n {isCancelledSubscription ? (\n \n \n {'Your subscription has been cancelled, you can renew it'}{' '}\n
\n
\n \n ) : null}\n \n {!paymentError && (\n \n {}}\n icon={ }\n >\n {planType === 'advanced' ? (\n \n {'Contact us for more'}\n \n ) : (\n Upgrade Plan\n )}\n \n {!!subscriptionId && !isCancelledSubscription ? (\n }\n >\n Cancel Subscription\n \n ) : null}\n \n )}\n \n \n\n \n \n {' '}\n Payment Information \n \n \n setSelectedKey(e?.key)}\n >\n }\n hidden={isPaymentInfoEmpty}\n >\n Update Payment Method\n \n \n \n \n }\n >\n \n
\n {isPaymentInfoEmpty ? (\n {'No Data Yet.'}
\n ) : (\n \n \n \n Billing details\n \n \n \n {billingDetails?.name}\n \n \n {billingDetails?.email}\n \n \n {billingDetails?.phone || 'N/A'}\n \n \n {billingDetails?.address?.country || 'N/A'}\n \n \n \n \n \n Payment Method\n \n {paymentError && (\n \n {' '}\n Your latest payment has failed. Update your payment method\n to continue this plan.\n \n )}\n \n {' '}\n {defaultPaymentMethod.card?.brand.toUpperCase()} card ending\n with {defaultPaymentMethod.card?.last4}\n \n \n Expires{' '}\n {defaultPaymentMethod.card?.exp_month}/\n {defaultPaymentMethod.card?.exp_year}\n \n \n
\n )}\n \n
\n \n\n Invoices}>\n \n {planType === 'free' ? (\n {'No Data Yet.'}
\n ) : Array.isArray(invoices) && invoices?.length > 0 ? (\n \n ) : (\n {'No Data Yet.'}
\n )}\n \n \n \n );\n};\n\nconst PlanAndPayments = () => {\n return (\n \n \n \n );\n};\n\nexport default PlanAndPayments;\n","import styled from 'styled-components';\nimport { Col, Form, Modal, Row } from 'antd';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\n\nexport const PageHeader = styled.h3`\n font-family: Helvetica;\n font-style: normal;\n font-weight: ${cssVariables.font.bold};\n font-size: 32px;\n line-height: 37px;\n`;\n\nexport const StyledPricing = styled(Row)``;\n\nexport const PlanCard = styled.div`\n border-radius: 8px;\n border: ${props =>\n props.isCurrentPlan\n ? `1px solid ${cssVariables.primaryCyan}`\n : `1px solid ${cssVariables.gray1}`};\n box-sizing: border-box;\n max-width: 350px;\n margin: 0 auto;\n`;\n\nexport const PlanHeader = styled.h4`\n display: flex;\n align-content: center;\n flex-wrap: wrap;\n padding: 16px 24px;\n padding-bottom: ${props => (props.isCurrentPlan ? '27px' : '20px')};\n text-transform: uppercase;\n font-size: 14px;\n font-weight: ${cssVariables.font.bold};\n font-family: Helvetica;\n background-color: ${props => props.color};\n border-radius: 8px 8px 0 0;\n color: #ffffff;\n`;\n\nexport const PlanCardContent = styled.div`\n font-family: Helvetica;\n display: flex;\n flex-direction: column;\n padding: 32px 20px;\n padding-top: 0;\n text-align: left;\n color: #66696f;\n`;\n\nexport const Features = styled.ul`\n margin: 0;\n padding: 0;\n list-style: none;\n\n > li {\n padding-top: 8px;\n font-size: 12px;\n\n > li span {\n margin-right: 20px;\n }\n }\n\n .anticon-check {\n font-weight: ${props =>\n props.isCurrentPlan ? cssVariables.font.bold : cssVariables.font.normal};\n color: ${props =>\n props.isCurrentPlan ? cssVariables.primaryCyan : 'inherit'};\n font-size: ${props => (props.isCurrentPlan ? '14px' : 'inherit')};\n }\n`;\n\nexport const StyledButton = styled(Button)`\n width: 100%;\n margin-top: 24px;\n background: ${props => (props.isSelected ? '#6D757D' : '#ffffff')} !important;\n border: 1.5px solid #eff3f4;\n font-size: 14px;\n font-weight: ${cssVariables.font.bold};\n color: ${props => (props.isSelected ? '#ffffff' : '#1667e7')} !important;\n`;\n\nexport const BottomText = styled.span`\n margin-top: 10px;\n font-size: 10px;\n font-weight: ${cssVariables.font.bold};\n text-transform: uppercase;\n text-align: center;\n`;\n\nexport const ContactUsSection = styled(Row)`\n width: 100%;\n padding: 5% 6%;\n background: #051e43;\n box-sizing: border-box;\n border-radius: 8px;\n bottom: 0;\n`;\n\nexport const ContactDetails = styled(Col)`\n > h5 {\n font-weight: ${cssVariables.font.bold};\n font-size: 2rem;\n line-height: 32px;\n color: ${cssVariables.gray5};\n }\n > p {\n font-size: 1.2rem;\n line-height: 24px;\n color: #e9ecef;\n letter-spacing: -0.02em;\n }\n`;\n\nexport const RightContent = styled.div``;\n\nexport const ContactUsButton = styled(Button)`\n font-size: 14px;\n font-weight: ${cssVariables.font.bold};\n width: 100%;\n background: #ffffff !important;\n color: #1667e7 !important;\n`;\n\nexport const StyledModal = styled(Modal)`\n font-family: Helvetica;\n`;\n\nexport const FormWrapper = styled(Form)`\n margin-top: 20px;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n`;\n\nexport const PaymentInformation = styled.div`\n width: 670px;\n margin-right: 24px;\n`;\n\nexport const PlanDetails = styled.div`\n width: 520px;\n margin-left: 24px;\n`;\n\nexport const BigText = styled.p`\n color: #40444b;\n font-size: 14px;\n font-weight: ${cssVariables.font.normal};\n line-height: 20px;\n letter-spacing: 0.04em;\n`;\n\nexport const PaymentInformationHeader = styled.h5`\n color: #000000;\n font-weight: ${cssVariables.font.bold};\n font-size: 18px;\n line-height: 24px;\n margin-bottom: 20px;\n`;\n\nexport const Divider = styled.div`\n border: 1px solid #d9dadb;\n margin: 24px 0;\n`;\n\nexport const CurrencySelection = styled.div`\n margin-top: 10px;\n font-weight: ${cssVariables.font.bold};\n`;\n\nexport const SmallText = styled.p`\n text-align: ${props => (props.textAlign ? props.textAlign : 'left')};\n color: #8c8f93;\n font-size: 13px !important;\n line-height: 24px;\n`;\n\nexport const LineItems = styled.div`\n display: flex;\n justify-content: space-between;\n\n > div > span {\n font-size: 18px;\n line-height: 24px;\n }\n`;\n\nexport const Total = styled.div`\n display: flex;\n justify-content: space-between;\n\n > div > span {\n font-size: 18px;\n line-height: 24px;\n }\n`;\n\nexport const PriceHeading = styled.div`\n display: flex;\n font-size: 48px;\n color: #000000;\n line-height: 55px;\n\n > span {\n font-size: 15px;\n transform: translateY(-12px);\n opacity: 0.5;\n }\n > p {\n font-size: 14px;\n color: #3d3f42;\n margin-left: 10px;\n }\n`;\n\nexport const SuccessMessage = styled.div`\n width: 368px;\n margin: 0 auto;\n\n > div {\n margin-top: 20px;\n }\n\n > img {\n display: block;\n margin: 0 auto;\n }\n > p {\n font-size: 17px;\n font-weight: ${cssVariables.font.normal};\n margin-top: 24px;\n }\n\n > ul li,\n > span {\n font-size: 16px;\n line-height: 24px;\n color: #33363c;\n }\n\n ${StyledButton} {\n background: #1667e7 !important;\n color: #ffffff !important;\n }\n`;\n\nexport const StyledCardElement = styled.div`\n padding: 12px 8px;\n border: 1px solid #e5e8e9;\n border-radius: 3px;\n width: ${props => (props.width ? props.width : 300)}px;\n`;\n\nexport const CardElementsContainer = styled.div`\n display: flex;\n justify-content: space-between;\n`;\n\nexport const StyledFormButtons = styled.div`\n display: flex;\n column-gap: 10px;\n\n button {\n width: 100%;\n }\n\n &:first-child {\n margin: 0px !important;\n }\n`;\n","import { useContext, useEffect, useState } from 'react';\nimport { useHistory, useLocation } from 'react-router-dom';\nimport { message } from 'antd';\n\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport ROUTES from 'constants/routes';\nimport { planDetailsSelector, planTypeSelector } from 'selectors/plan';\nimport { getTokenSelector } from 'selectors/user';\nimport { stripeAPIService } from 'services/stripeAPI.service';\nimport {\n SET_STRIPE_STATUS,\n SET_USER_SUBSCRIPTION,\n SHOW_CANCEL_SUBSCRIPTION_MODAL,\n SHOW_SWITCH_PLAN_SUBSCRIPTION_MODAL,\n} from 'store/action';\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\nimport {\n isCancelledSubscriptionSelector,\n isProcessingSelector,\n isTrialSubscriptionSelector,\n trialDaysLeftSelector,\n} from 'selectors/subscription';\nimport {\n AUTO_RENEW_SUCCESS_MESSAGE,\n CANCELED_CHECKOUT_URL_PARAM,\n DOWNGRADE_SUCCESS_MESSAGE,\n UPGRADE_SUCCESS_MESSAGE,\n} from 'constants/plan';\nimport { useQuery } from 'pages/useQuery';\nimport { parseBoolean } from 'utils/dataTypes';\n\nconst usePricing = () => {\n const [state, dispatch] = useContext(Context);\n const { graph, sentinel } = state;\n const isCancelledSubscription = useSelector(isCancelledSubscriptionSelector);\n const isProcessing = useSelector(isProcessingSelector);\n const isTrialSubscription = useSelector(isTrialSubscriptionSelector);\n const planDetails = useSelector(planDetailsSelector);\n const planType = useSelector(planTypeSelector);\n const token = useSelector(getTokenSelector);\n const trialDaysLeft = useSelector(trialDaysLeftSelector);\n\n const { push } = useHistory();\n const { pathname } = useLocation();\n const query = useQuery();\n\n const [isSubscriptionLoading, setIsSubscriptionLoading] = useState(false);\n const [selectedPlan, setSelectedPlan] = useState(null);\n const [isSuccessModalVisible, setSuccessMessageVisible] = useState(false);\n\n const SUCCESS_URL = `${window.location.origin}${ROUTES.SUBSCRIPTION}`;\n const CANCEL_URL = `${window.location.origin}${ROUTES.SUBSCRIPTION}${CANCELED_CHECKOUT_URL_PARAM}`;\n\n const getCurrentStripeStatus = async () => {\n try {\n const res = await stripeAPIService.getSubscriptionStatus(\n graph,\n sentinel,\n token\n );\n if (res.data?.report) {\n dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n }\n } catch (error) {\n message.error(error?.message || GET_DATA_ERROR);\n }\n };\n\n const getStripeStatus = async () => {\n try {\n const res = await stripeAPIService.getStripeStatus(\n graph,\n sentinel,\n token\n );\n if (res.data?.report) {\n dispatch({\n type: SET_STRIPE_STATUS,\n payload: res.data.report[0],\n });\n }\n } catch (error) {\n message.error(GET_DATA_ERROR);\n }\n };\n\n const fetchSubscriptionStatus = async () => {\n try {\n const res = await stripeAPIService.getSubscriptionStatus(\n graph,\n sentinel,\n token\n );\n if (res.data?.report) {\n dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n }\n } catch (error) {\n message.error(error?.message || GET_DATA_ERROR);\n }\n };\n\n useEffect(() => {\n async function fetchData() {\n await fetchSubscriptionStatus();\n }\n if (planType !== 'free' || isTrialSubscription) {\n fetchData();\n }\n }, [planType, isTrialSubscription]);\n\n useEffect(() => {\n const fetchStripeData = async () => {\n await getCurrentStripeStatus();\n await getStripeStatus();\n };\n\n if (planType !== 'free' || isTrialSubscription) {\n fetchStripeData();\n }\n }, []);\n\n const changeStripeIsProcessingStatus = async isProcessing => {\n try {\n const res = await stripeAPIService.changeStripeIsProcessingStatus(\n isProcessing,\n graph,\n sentinel,\n token\n );\n dispatch({\n type: SET_STRIPE_STATUS,\n payload: res.data.report[0],\n });\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleCancelSubscription = async () => {\n dispatch({\n type: SHOW_CANCEL_SUBSCRIPTION_MODAL,\n });\n };\n\n const handleUpgradeSubscription = async newPlan => {\n dispatch({\n type: SHOW_SWITCH_PLAN_SUBSCRIPTION_MODAL,\n payload: newPlan,\n });\n };\n\n useEffect(() => {\n if (query && query.get('checkout-success')) {\n const isCheckoutSuccess = parseBoolean(query.get('checkout-success'));\n changeStripeIsProcessingStatus(isCheckoutSuccess);\n }\n }, [query]);\n\n const onCloseSubscriptionMessage = () => {\n setSuccessMessageVisible(false);\n push(ROUTES.HOME);\n };\n\n const handleSwitchPlan = async newPlan => {\n setIsSubscriptionLoading(true);\n try {\n if (planType === newPlan && !isCancelledSubscription) {\n await handleCancelSubscription();\n } else if (newPlan === planType && isTrialSubscription) {\n const res = await stripeAPIService.autoRenewSubscription(\n graph,\n sentinel,\n token\n );\n\n if (res.data?.report[0]) {\n await dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n setIsSubscriptionLoading(false);\n return message.success(AUTO_RENEW_SUCCESS_MESSAGE);\n }\n } else if (planType !== 'advanced' && newPlan === 'advanced') {\n await handleUpgradeSubscription({\n planType: newPlan,\n action: 'upgrade',\n });\n } else if (planType === 'free' && !isCancelledSubscription) {\n await handleUpgradeSubscription({\n planType: newPlan,\n action: 'upgrade',\n });\n } else if (planType !== 'basic' && newPlan === 'basic') {\n await handleUpgradeSubscription({\n planType: newPlan,\n action: 'downgrade',\n });\n } else {\n const RETURN_PATH = `${window.location.origin}${pathname}${CANCELED_CHECKOUT_URL_PARAM}`;\n const res = await stripeAPIService.switchPlan(\n newPlan,\n SUCCESS_URL,\n RETURN_PATH || CANCEL_URL,\n graph,\n sentinel,\n token\n );\n\n const checkoutURl = res.data?.report[0]?.url;\n if (checkoutURl) {\n window.location.href = checkoutURl;\n } else if (res.data?.success) {\n await dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n message.success(\n (!isCancelledSubscription ||\n (isTrialSubscription && !isCancelledSubscription)) &&\n planType === newPlan\n ? AUTO_RENEW_SUCCESS_MESSAGE\n : trialDaysLeft &&\n !isCancelledSubscription &&\n planType !== 'advanced'\n ? UPGRADE_SUCCESS_MESSAGE\n : DOWNGRADE_SUCCESS_MESSAGE\n );\n } else {\n throw new Error(GET_DATA_ERROR);\n }\n }\n } catch (error) {\n message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n setIsSubscriptionLoading(false);\n };\n\n return {\n handleSwitchPlan,\n isCancelledSubscription,\n isProcessing,\n isSubscriptionLoading,\n isSuccessModalVisible,\n isTrialSubscription,\n onCloseSubscriptionMessage,\n planDetails,\n planType,\n selectedPlan,\n setSuccessMessageVisible,\n trialDaysLeft,\n };\n};\n\nexport default usePricing;\n","import React, { useCallback } from 'react';\nimport { Alert, Col, Spin, Tag } from 'antd';\nimport { CheckOutlined } from '@ant-design/icons';\n\nimport {\n PriceHeading,\n StyledPricing,\n PlanCard,\n PlanHeader,\n PlanCardContent,\n Features,\n StyledButton,\n BottomText,\n ContactUsSection,\n LeftContent,\n RightContent,\n ContactUsButton,\n StyledModal,\n SuccessMessage,\n ContactDetails,\n} from './Pricing.style';\nimport RobotIcon from 'assets/images/robot-icon-black-small.svg';\nimport { ZSB_CONTACT } from 'constants/outboundLinks';\nimport usePricing from './hooks';\nimport {\n IS_PROCESSING_SUBSCRIPTION,\n START_TRIAL_DAYS_MESSAGE,\n} from 'constants/plan';\nimport { detailedPlanFeatures } from 'pages/UserProfile/planDetails';\n\nconst Pricing = () => {\n const {\n handleSwitchPlan,\n isCancelledSubscription,\n isProcessing,\n isSubscriptionLoading,\n isSuccessModalVisible,\n isTrialSubscription,\n onCloseSubscriptionMessage,\n planDetails,\n planType,\n selectedPlan,\n setSuccessMessageVisible,\n trialDaysLeft,\n } = usePricing();\n\n const buttonPlanValue = useCallback(\n buttonPlan => {\n switch (buttonPlan) {\n case 'basic': {\n if (planType === 'free' && !isCancelledSubscription) {\n return 'Subscribe';\n } else if (planType === 'advanced' && !isCancelledSubscription) {\n return 'Downgrade';\n } else if (planType === buttonPlan && !isCancelledSubscription) {\n return 'Cancel Subscription';\n } else if (planType === buttonPlan && isCancelledSubscription) {\n return 'Renew';\n } else {\n return START_TRIAL_DAYS_MESSAGE;\n }\n }\n\n case 'advanced': {\n if (\n ['free', 'basic'].includes(planType) &&\n !isCancelledSubscription\n ) {\n return 'Upgrade';\n } else if (\n planType === 'basic' &&\n !isCancelledSubscription &&\n isTrialSubscription\n ) {\n return 'Upgrade';\n } else if (planType === buttonPlan && !isCancelledSubscription) {\n return 'Cancel Subscription';\n } else if (planType === buttonPlan && isCancelledSubscription) {\n return 'Renew';\n } else {\n return START_TRIAL_DAYS_MESSAGE;\n }\n }\n\n default:\n return null;\n }\n },\n [isTrialSubscription, planType, isCancelledSubscription]\n );\n\n return (\n \n \n \n \n \n {'Starter'}{' '}\n {planType === 'free' && !isCancelledSubscription && (\n \n Current Plan\n \n )}\n \n \n \n {isProcessing && planType === 'free' ? (\n \n ) : (\n <>\n <>{'Free'}>\n {'forever'}
\n >\n )}\n \n \n {detailedPlanFeatures(planDetails.free, 'free', true).map(\n (feature, key) => (\n \n {feature}\n \n )\n )}\n \n \n \n \n \n \n \n <>\n {isTrialSubscription && planType === 'basic'\n ? `Basic Trial ${\n !!trialDaysLeft && !isCancelledSubscription\n ? trialDaysLeft + ' day(s) left'\n : ''\n }`\n : 'Basic'}\n \n {isCancelledSubscription && planType === 'basic'\n ? ` (CANCELLED)`\n : null}\n \n >{' '}\n {planType === 'basic' && !isCancelledSubscription && (\n \n Current Plan\n \n )}{' '}\n \n \n \n {isProcessing && planType === 'basic' ? (\n \n ) : (\n <>\n $ \n 40\n per month
\n >\n )}\n \n \n {detailedPlanFeatures(planDetails.basic, 'basic', true).map(\n (feature, key) => (\n \n {feature}\n \n )\n )}\n \n handleSwitchPlan('basic')}\n />\n\n {'credit card required'} \n \n \n \n\n \n \n \n {isTrialSubscription && planType === 'advanced'\n ? `Advanced Trial ${\n !!trialDaysLeft && !isCancelledSubscription\n ? trialDaysLeft + ' day(s) left'\n : ''\n }`\n : 'Advanced'}\n \n {isCancelledSubscription && planType === 'advanced'\n ? ` (CANCELLED)`\n : null}\n {' '}\n {planType === 'advanced' && !isCancelledSubscription && (\n \n Current Plan\n \n )}\n \n\n \n \n {isProcessing && planType === 'advanced' ? (\n \n ) : (\n <>\n $ \n 200\n per month
\n >\n )}\n \n \n {detailedPlanFeatures(\n planDetails.advanced,\n 'advanced',\n true\n ).map((feature, key) => (\n \n {feature}\n \n ))}\n \n handleSwitchPlan('advanced')}\n hidden={planType !== 'advanced' && isCancelledSubscription}\n />\n {'credit card required'} \n \n \n \n \n \n \n {`Can't find the right plan?`} \n \n {`We'd be more than happy to find a solution for your organisation`}\n
\n \n \n \n \n \n \n \n setSuccessMessageVisible(false)}\n onCancel={() => setSuccessMessageVisible(false)}\n height={'100%'}\n footer={null}\n >\n \n \n \n Thank you for starting your free trial to{' '}\n {planType.charAt(0).toUpperCase() + planType.slice(1)} Plan :)\n
\n With a {selectedPlan} plan, you now have: \n \n {selectedPlan &&\n planDetails[selectedPlan].shortFeatures.map((sf, key) => (\n {sf} \n ))}\n \n \n \n \n \n );\n};\n\nexport default Pricing;\n// TODO: clean up the modal in this component;\n","import { useState, useContext } from 'react';\nimport { message } from 'antd';\n\nimport {\n showCancelSubscriptionModalSelector,\n showCancelledSubscriptionSuccessMessageSelector,\n trialDaysLeftSelector,\n} from 'selectors/subscription';\nimport { getTokenSelector } from 'selectors/user';\nimport {\n CANCEL_SUBSCRIPTION,\n CLOSE_CANCEL_SUBSCRIPTION_MODAL,\n CLOSE_SUCCESS_CANCEL_SUBSCRIPTION_MODAL,\n SET_USER_SUBSCRIPTION,\n} from 'store/action';\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\nimport { stripeAPIService } from 'services/stripeAPI.service';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport ROUTES from 'constants/routes';\n\nconst useCancelSubscriptionModal = () => {\n const [state, dispatch] = useContext(Context);\n const trialDaysLeft = useSelector(trialDaysLeftSelector);\n const showCancelledSubscriptionSuccessMessage = useSelector(\n showCancelledSubscriptionSuccessMessageSelector\n );\n const showCancelSubscriptionModal = useSelector(\n showCancelSubscriptionModalSelector\n );\n const token = useSelector(getTokenSelector);\n const { graph, sentinel } = state;\n\n const [loading, setLoading] = useState(false);\n\n const handleCloseModal = () => {\n if (showCancelledSubscriptionSuccessMessage) {\n dispatch({\n type: CLOSE_SUCCESS_CANCEL_SUBSCRIPTION_MODAL,\n });\n message.success('Successfully cancelled your subscription!');\n window.location.href = ROUTES.HOME;\n } else {\n dispatch({\n type: CLOSE_CANCEL_SUBSCRIPTION_MODAL,\n });\n }\n };\n\n const handleCancelSubscription = async () => {\n setLoading(true);\n try {\n const res = await stripeAPIService.cancelSubscription(\n graph,\n sentinel,\n token\n );\n\n if (res.data?.report[0]) {\n await dispatch({\n type: CANCEL_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n }\n\n const newsubscriptionRes = await stripeAPIService.getSubscriptionStatus(\n graph,\n sentinel,\n token\n );\n if (res.data?.report) {\n await dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: newsubscriptionRes.data.report[0],\n });\n }\n setLoading(false);\n } catch (error) {\n setLoading(false);\n return message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n return {\n handleCloseModal,\n handleCancelSubscription,\n loading,\n showCancelSubscriptionModal,\n showCancelledSubscriptionSuccessMessage,\n trialDaysLeft,\n };\n};\n\nexport default useCancelSubscriptionModal;\n","import React, { useMemo } from 'react';\nimport { Link } from 'react-router-dom';\nimport { ExclamationCircleTwoTone } from '@ant-design/icons';\n\nimport Modal from '../GenericModal';\nimport { cssVariables } from 'styles/root';\nimport { StyledFlexRowLeft } from 'styles/GenericStyledComponents';\nimport { SuccessMessage } from 'pages/UserProfile/PlanAndPayments/PlanAndPayments.style';\nimport Button from 'components/Button';\nimport ROUTES from 'constants/routes';\nimport useCancelSubscriptionModal from './hooks';\n\nconst CancelSubscriptionModal = () => {\n const {\n handleCloseModal,\n handleCancelSubscription,\n loading,\n showCancelSubscriptionModal,\n showCancelledSubscriptionSuccessMessage,\n trialDaysLeft,\n } = useCancelSubscriptionModal();\n\n const Content = useMemo(() => {\n const deactivatingBotNotice = (\n {`Your existing bots will be inactive${\n trialDaysLeft ? ' in ' + trialDaysLeft + ' days.' : '.'\n }`}
\n );\n if (showCancelledSubscriptionSuccessMessage) {\n return (\n <>\n \n {'Subscription Cancelled!'} \n {deactivatingBotNotice}\n \n {`You can always `}\n subscribe to a plan\n {` if you\n change your mind :)`}\n
\n \n \n >\n );\n } else if (showCancelSubscriptionModal) {\n return (\n <>\n \n \n {'Are you sure you want to cancel your subscription?'} \n \n {deactivatingBotNotice}\n >\n );\n }\n }, [showCancelledSubscriptionSuccessMessage, showCancelSubscriptionModal]);\n\n return (\n \n {Content}\n \n );\n};\n\nexport default CancelSubscriptionModal;\n","import { useState, useContext } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { message } from 'antd';\n\nimport {\n switchPlanSelector,\n showSwitchPlanSubscriptionModalSelector,\n showUpgradedSubscriptionSuccessMessageSelector,\n} from 'selectors/subscription';\nimport { getTokenSelector, isExternalPageSelector } from 'selectors/user';\nimport {\n CLOSE_SWITCH_PLAN_SUBSCRIPTION_MODAL,\n SET_USER_SUBSCRIPTION,\n} from 'store/action';\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\nimport { stripeAPIService } from 'services/stripeAPI.service';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport ROUTES from 'constants/routes';\nimport {\n CANCELED_CHECKOUT_URL_PARAM,\n DOWNGRADE_SUCCESS_MESSAGE,\n UPGRADE_SUCCESS_MESSAGE,\n} from 'constants/plan';\n\nconst PAGE_BASE_URL = process.env.REACT_APP_PAGE_BASE_URL;\n\nconst useSwitchPlanSubscriptionModal = () => {\n const [state, dispatch] = useContext(Context);\n const { pathname } = useLocation();\n const showUpgradedSubscriptionSuccessMessage = useSelector(\n showUpgradedSubscriptionSuccessMessageSelector\n );\n const showSwitchPlanSubscriptionModal = useSelector(\n showSwitchPlanSubscriptionModalSelector\n );\n const switchPlan = useSelector(switchPlanSelector);\n const token = useSelector(getTokenSelector);\n const isExternalPage = useSelector(isExternalPageSelector);\n\n const { graph, sentinel } = state;\n const [loading, setLoading] = useState(false);\n\n const SUCCESS_URL = `${window.location.origin}${ROUTES.SUBSCRIPTION}`;\n const CANCEL_URL = `${window.location.origin}${ROUTES.SUBSCRIPTION}${CANCELED_CHECKOUT_URL_PARAM}`;\n\n const handleCloseModal = () => {\n dispatch({\n type: CLOSE_SWITCH_PLAN_SUBSCRIPTION_MODAL,\n });\n };\n\n const handleUpgradeSubscription = async () => {\n setLoading(true);\n try {\n if (isExternalPage) {\n window.open(`${PAGE_BASE_URL}profile/subscription`, '_black');\n } else {\n const RETURN_PATH = `${window.location.origin}${pathname}${CANCELED_CHECKOUT_URL_PARAM}`;\n const res = await stripeAPIService.switchPlan(\n switchPlan.planType,\n SUCCESS_URL,\n RETURN_PATH || CANCEL_URL,\n graph,\n sentinel,\n token\n );\n\n const checkoutURl = res.data?.report[0]?.url;\n if (checkoutURl) {\n window.location.href = checkoutURl;\n } else if (res.data?.success) {\n await dispatch({\n type: SET_USER_SUBSCRIPTION,\n payload: res.data.report[0],\n });\n message.success(\n switchPlan.action === 'upgrade'\n ? UPGRADE_SUCCESS_MESSAGE\n : DOWNGRADE_SUCCESS_MESSAGE\n );\n } else {\n throw new Error(GET_DATA_ERROR);\n }\n }\n setLoading(false);\n } catch (error) {\n setLoading(false);\n return message.error(error?.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n return {\n handleCloseModal,\n handleUpgradeSubscription,\n loading,\n showSwitchPlanSubscriptionModal,\n showUpgradedSubscriptionSuccessMessage,\n switchPlan,\n };\n};\n\nexport default useSwitchPlanSubscriptionModal;\n","import React, { useMemo } from 'react';\nimport { ExclamationCircleTwoTone } from '@ant-design/icons';\n\nimport Modal from '../GenericModal';\nimport { cssVariables } from 'styles/root';\nimport { StyledFlexRowLeft } from 'styles/GenericStyledComponents';\nimport { SuccessMessage } from 'pages/UserProfile/PlanAndPayments/PlanAndPayments.style';\nimport Button from 'components/Button';\nimport useSwitchPlanSubscriptionModal from './hooks';\nimport { UPGRADE_SUCCESS_MESSAGE } from 'constants/plan';\n\nconst SwitchPlanSubscriptionModal = () => {\n const {\n handleCloseModal,\n handleUpgradeSubscription,\n loading,\n showSwitchPlanSubscriptionModal,\n showUpgradedSubscriptionSuccessMessage,\n switchPlan,\n } = useSwitchPlanSubscriptionModal();\n\n const Content = useMemo(() => {\n if (showUpgradedSubscriptionSuccessMessage) {\n return (\n <>\n {UPGRADE_SUCCESS_MESSAGE} \n \n >\n );\n } else if (showSwitchPlanSubscriptionModal) {\n return (\n <>\n \n \n {`Are you sure you want to ${switchPlan.action} your subscription?`} \n \n >\n );\n }\n }, [showUpgradedSubscriptionSuccessMessage, showSwitchPlanSubscriptionModal]);\n\n return (\n \n {Content}\n \n );\n};\n\nexport default SwitchPlanSubscriptionModal;\n","import { useContext, useState } from 'react';\nimport { message } from 'antd';\n\nimport { apiService } from 'services/api.service';\nimport { Context } from 'store/store';\nimport {\n INITIALIZE_API_KEY,\n SET_PUBLIC_INFO,\n SET_PUBLIC_KEYS,\n SPAWN_CREATE,\n UNSET_API_KEY,\n} from 'store/action';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { stripUUID } from 'utils';\n\nconst useApiKey = () => {\n const [state, dispatch] = useContext(Context);\n const token = useSelector(getTokenSelector);\n const [apiKeyloading, setApiKeyLoading] = useState(false);\n const { apiGatewayConfig, publicKey, pubAskedQuestion, sentinel } = state;\n\n const initializeApiGateway = async (key, askedQuestion) => {\n const res = await apiService.initializeApiGateway(\n sentinel,\n token,\n key,\n stripUUID(askedQuestion)\n );\n\n dispatch({\n type: INITIALIZE_API_KEY,\n payload: {\n apiConfig: res.data?.report[0],\n isIntegrationComponent: false,\n },\n });\n };\n\n const spawnCreate = async (pK, pAQ) => {\n const apiName = 'zsb_public_api';\n try {\n const res = await apiService.spawnCreate(apiName, token);\n const pkRes = await apiService.getPublicKey(token);\n pK = pkRes.data.anyone;\n pAQ = res.data.jid;\n\n await initializeApiGateway(pK, pAQ);\n\n dispatch({\n type: SPAWN_CREATE,\n payload: {\n publicKey: pK,\n pubAskedQuestion: pAQ,\n },\n });\n\n return [pK, pAQ];\n } catch (error) {\n throw new Error('Something went wrong while creating the integration');\n }\n };\n\n const handleEnableApiKey = async () => {\n setApiKeyLoading(true);\n try {\n if (!publicKey) {\n await spawnCreate(publicKey, pubAskedQuestion);\n } else {\n await initializeApiGateway(publicKey, pubAskedQuestion);\n }\n setApiKeyLoading(false);\n } catch (error) {\n setApiKeyLoading(false);\n return message.error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n const handleDisableApiKey = async () => {\n setApiKeyLoading(true);\n if (!publicKey) {\n try {\n [publicKey, pubAskedQuestion] = await spawnCreate(\n publicKey,\n pubAskedQuestion\n );\n } catch (error) {\n setApiKeyLoading(false);\n return message.error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n }\n\n try {\n await apiService.removeAPIGateway(sentinel, token);\n dispatch({\n type: UNSET_API_KEY,\n });\n\n setApiKeyLoading(false);\n } catch (error) {\n setApiKeyLoading(false);\n return message.error(error.message || DEFAULT_ERROR_MESSAGE);\n }\n };\n\n return {\n apiGatewayConfig,\n apiKeyloading,\n handleDisableApiKey,\n handleEnableApiKey,\n message,\n };\n};\n\nexport default useApiKey;\n","import { cssVariables } from 'styles/root';\nimport styled from 'styled-components';\nimport { Divider, Collapse } from 'antd';\nimport Button from 'components/Button';\n\nconst { Panel } = Collapse;\n\nexport const StyledCodeButton = styled(Button)`\n background-color: ${cssVariables.primaryBlue} !important;\n text-transform: capitalize;\n max-width: 200px;\n color: #fff !important;\n\n &.secondary {\n color: ${cssVariables.primaryBlue} !important;\n background-color: transparent !important;\n }\n\n & span {\n font-weight: ${cssVariables.font.normal};\n letter-spacing: 0px;\n }\n`;\n\nexport const StyledActionBtns = styled.div`\n display: flex;\n column-gap: 10px;\n\n & button {\n text-transform: capitalize;\n }\n`;\n\nexport const StyledDivider = styled(Divider)`\n margin: 20px;\n`;\n\nexport const StyledEncircledNumber = styled.div`\n width: 25px;\n height: 25px;\n border-radius: 50%;\n border: 1px solid ${cssVariables.gray1};\n text-align: center;\n color: #0c3980;\n font-wize: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0px 5px;\n`;\n\nexport const StyledSnippets = styled.div`\n background: rgb(191, 221, 255, 0.1);\n padding: 20px;\n border-radius: 5px;\n margin-bottom: 10px;\n\n & > * {\n opacity: 1;\n }\n\n & span {\n display: flex;\n margin: 5px 0;\n }\n`;\n\nexport const StyledCodeBlock = styled.div`\n max-width: 100%;\n border-radius: 5px;\n\n & pre {\n max-width: 100%;\n width: 100%;\n word-break: break-all;\n }\n`;\n\nexport const StyledApiKeyContainer = styled.div`\n width: 100%;\n\n & form {\n display: flex;\n flex-direction: column;\n row-gap: 16px;\n }\n`;\n\nexport const StyledApiKeySubContainer = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 14px;\n`;\n\nexport const StyledCollapsiblePanel = styled(Panel)`\n & .ant-collapse-content-box {\n padding: 30px;\n }\n`;\n\nexport const StyledCollapsiblePanelHeader = styled.div`\n display: flex;\n align-items: center;\n\n & span {\n font-size: 24px;\n }\n`;\n\nexport const StyledPanelSubHeader = styled.div`\n margin-bottom: 10px;\n color: ${cssVariables.gray0};\n font-weight: 700;\n font-size: 20px;\n`;\n\nexport const StyledPageTitle = styled.div`\n text-align: left;\n width: 100%;\n & h3 {\n display: flex;\n width: 100%;\n font-size: 24px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\n\nexport const StyledContent = styled.div`\n display: flex;\n height: auto;\n background: #fff;\n padding: 30px;\n width: 100%;\n flex-direction: column;\n\n & .ant-alert {\n margin: 20px;\n }\n\n & h2 {\n font-size: 18px;\n font-weight: ${cssVariables.font.normal};\n }\n`;\n","import { useContext } from 'react';\nimport {\n apiKeyModalSelector,\n isApiKeyModalOpenSelector,\n} from 'selectors/bot/ui';\nimport { CLOSE_API_KEY_MODAL } from 'store/action';\n\nimport { Context } from 'store/store';\nimport useSelector from 'store/useSelector';\n\nconst useApiKeyModal = () => {\n const [state, dispatch] = useContext(Context);\n\n const isApiKeyModalOpen = useSelector(isApiKeyModalOpenSelector);\n const apiKeyModal = useSelector(apiKeyModalSelector);\n const { apiGatewayConfig } = state;\n\n const handleCloseModal = () => {\n dispatch({ type: CLOSE_API_KEY_MODAL });\n };\n\n return {\n isApiKeyModalOpen,\n apiKeyModal,\n handleCloseModal,\n apiGatewayConfig,\n };\n};\n\nexport default useApiKeyModal;\n","import styled from 'styled-components';\n\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledApiKey = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n`;\n\nexport const StyledCodeButton = styled(Button)`\n background-color: ${cssVariables.primaryBlue} !important;\n text-transform: capitalize;\n max-width: 200px;\n\n &.secondary {\n color: ${cssVariables.primaryBlue} !important;\n background-color: transparent !important;\n }\n\n & span {\n font-weight: ${cssVariables.font.normal};\n letter-spacing: 0px;\n }\n`;\n\nexport const StyledCodeBlock = styled.div`\n max-width: 100%;\n border-radius: 5px;\n\n & pre {\n max-width: 100%;\n width: 100%;\n word-break: break-all;\n }\n`;\n","import React from 'react';\n\nimport CopyToClipboard from 'react-copy-to-clipboard';\nimport { Typography, message } from 'antd';\nimport Modal from 'components/Modals/GenericModal';\nimport useApiKeyModal from './hooks';\nimport {\n StyledApiKey,\n StyledCodeBlock,\n StyledCodeButton,\n} from './ApiKeyModal.styles';\n\nconst { Paragraph } = Typography;\n\nconst ApiKeyModal = () => {\n const { apiGatewayConfig, isApiKeyModalOpen, handleCloseModal } =\n useApiKeyModal();\n\n const RenderUserSettingsApiKey = () => (\n <>\n \n Here's the generated API Key.\n \n message.info('Copied to clipboard.')}\n />\n \n \n \n \n \n {apiGatewayConfig?.auth}
\n \n \n \n >\n );\n\n return (\n \n \n \n );\n};\n\nexport default ApiKeyModal;\n","import React from 'react';\nimport { Typography, Spin, Empty } from 'antd';\nimport { CopyToClipboard } from 'react-copy-to-clipboard';\nimport useApiKey from './hooks';\nimport {\n StyledFlexColumn,\n StyledFlexRowLeft,\n StyledSpaceBetweenFlexCenter,\n} from 'styles/GenericStyledComponents';\nimport {\n StyledContent,\n StyledPageTitle,\n StyledCodeButton,\n StyledSnippets,\n StyledApiKeyContainer,\n StyledApiKeySubContainer,\n StyledPanelSubHeader,\n StyledCodeBlock,\n} from './ApiKey.styles';\nimport Button from 'components/Button';\nimport ApiKeyModal from 'components/Modals/ApiKeyModal';\n\nconst { Paragraph } = Typography;\nconst ApiKey = () => {\n const {\n apiGatewayConfig,\n apiKeyloading,\n handleDisableApiKey,\n handleEnableApiKey,\n message,\n } = useApiKey();\n\n return (\n \n \n \n \n \n API Key \n \n {(apiGatewayConfig?.auth || apiGatewayConfig?.auth) && (\n \n )}\n {apiGatewayConfig?.auth && (\n \n )}\n \n \n \n {apiGatewayConfig && (\n \n \n {`Configuration`} \n \n \n \n {'Auth:'}\n {!apiGatewayConfig?.auth.includes('****') && (\n \n message.info('Copied to clipboard.')}\n />\n \n )}\n \n \n \n \n \n {apiGatewayConfig?.auth}{' '}\n {apiGatewayConfig?.auth.includes('****') &&\n ``}\n
\n \n \n \n\n \n {'Endpoint:'}\n \n message.info('Copied to clipboard.')}\n />\n \n \n \n \n \n {apiGatewayConfig?.url}
\n \n \n \n\n \n {'Key'}\n \n message.info('Copied to clipboard.')}\n />\n \n \n \n \n \n {apiGatewayConfig?.key}
\n \n \n \n \n \n )}\n {!apiGatewayConfig && (\n No configuration found.}\n >\n \n \n )}\n \n \n \n \n );\n};\nexport default ApiKey;\n","import React, { useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nimport ROUTES from 'constants/routes';\nimport Layout from 'components/Layout';\nimport MyAccount from './MyAccount';\nimport UserSidebar from 'components/Sidebar/UserSidebar';\nimport { StyledFlexRowCenter } from 'styles/GenericStyledComponents';\nimport ActivityLogs from 'components/ActivityLogs';\nimport OnboardingFlags from './OnboardingFlags';\nimport PlanAndPayments from './PlanAndPayments';\nimport Pricing from './Subscription/Pricing';\nimport useSelector from 'store/useSelector';\nimport {\n showCancelSubscriptionModalSelector,\n showSwitchPlanSubscriptionModalSelector,\n} from 'selectors/subscription';\nimport CancelSubscriptionModal from 'components/Modals/CancelSubscriptionModal';\nimport SwitchPlanSubscriptionModal from 'components/Modals/SwitchPlanSubscriptionModal';\nimport ApiKey from './ApiKey';\n\nconst UserProfile = () => {\n const showCancelSubscriptionModal = useSelector(\n showCancelSubscriptionModalSelector\n );\n const showSwitchPlanSubscriptionModal = useSelector(\n showSwitchPlanSubscriptionModalSelector\n );\n const { pathname } = useLocation();\n\n const getContent = useMemo(() => {\n switch (pathname) {\n case ROUTES.USER_PROFILE:\n return ;\n\n case ROUTES.ACTIVITY_LOGS:\n return ;\n\n case ROUTES.ONBOARDING_FLAGS:\n return ;\n\n case ROUTES.PLAN_AND_PAYMENTS:\n return ;\n\n case ROUTES.SUBSCRIPTION:\n return ;\n\n case ROUTES.API_KEY:\n return ;\n\n default:\n return ;\n }\n }, [pathname]);\n\n return (\n \n }>{getContent} \n {showCancelSubscriptionModal ? : null}\n {showSwitchPlanSubscriptionModal ? : null}\n \n );\n};\n\nexport default UserProfile;\n","import ROUTES from 'constants/routes';\nimport { useEffect, useState } from 'react';\nimport { useLocation, useHistory } from 'react-router-dom';\nimport { isExternalPageSelector } from 'selectors/user';\nimport { apiService } from 'services/api.service';\nimport useSelector from 'store/useSelector';\n\nconst useVerifyEmail = () => {\n const { search, state } = useLocation();\n // `ROUTES.VERIFY_EMAIL = /activation`\n // finds and get the value the query (after `?`)\n const query = new URLSearchParams(search);\n\n // gets and stores the value of `key` query\n const key = query.get('key');\n const history = useHistory();\n const [code, setCode] = useState(key);\n const [error, setError] = useState(null);\n const [verified, setVerified] = useState(false);\n const [loading, setLoading] = useState(false);\n const isExternalPage = useSelector(isExternalPageSelector);\n\n useEffect(() => {\n if ((!state || !state.email) && !code) {\n history.push(ROUTES.HOME);\n }\n\n if (key) {\n handleVerify(null, key);\n }\n }, []);\n\n const handleChangeCode = e => {\n setCode(e.target.value);\n };\n\n const handleVerify = async e => {\n if (e) {\n e.preventDefault();\n }\n setLoading(true);\n const response = await apiService.userActivate(code);\n if (response.status === 200) {\n setVerified(true);\n setError(null);\n } else {\n setVerified(false);\n setError(`${response.status.statusText + '.' || ''} Please try again.`);\n }\n return setLoading(false);\n };\n\n return {\n error,\n key,\n history,\n loading,\n verified,\n state,\n handleChangeCode,\n handleVerify,\n setCode,\n isExternalPage,\n };\n};\n\nexport default useVerifyEmail;\n","import React from 'react';\nimport styled from 'styled-components';\nimport Input from 'components/Input';\nimport { Button } from 'antd';\nimport Alert from 'components/Alert';\nimport ROUTES from 'constants/routes';\nimport Logo from 'assets/images/zeroshotbot_black.png';\nimport Header from 'components/Header';\nimport { cssVariables } from 'styles/root';\nimport useVerifyEmail from './hooks';\nimport CountDownRedirectModal from 'components/Modals/RedirectModal';\n\nconst Wrapper = styled.div`\n width: 472px;\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n margin-top: 24px;\n display: flex;\n height: 100vh;\n justify-content: center;\n text-align: center;\n row-gap: 20px;\n\n form {\n width: 100%;\n margin-top: 24px;\n display: flex;\n flex-direction: column;\n row-gap: 20px;\n }\n\n h1 {\n text-align: center;\n font-size: 16px;\n font-weight: ${cssVariables.font.bold};\n color: ${cssVariables.green1};\n }\n`;\n\nconst StyledButton = styled(Button)`\n width: 100%;\n`;\n\nconst StyledLogo = styled.img`\n max-width: 300px;\n margin-bottom: 30px;\n align-self: center;\n`;\n\nconst VerifyEmail = () => {\n const {\n code,\n error,\n key,\n loading,\n state,\n verified,\n handleChangeCode,\n handleVerify,\n history,\n isExternalPage,\n } = useVerifyEmail();\n\n return (\n <>\n \n \n {!key && !verified ? (\n <>\n {`An activation code has been sent to your email: `}
\n {state && state.email && {state.email} }\n {error && }\n \n >\n ) : null}\n {verified ? (\n <>\n \n Success! Your account has been successfully verified. \n \n history.push({\n pathname: isExternalPage\n ? ROUTES.EXTERNAL_LOGIN\n : ROUTES.LOGIN,\n state: { email: state && state.email ? state.email : null },\n })\n }\n >\n {`Login to build a bot!`}\n \n \n >\n ) : null}\n \n >\n );\n};\n\nVerifyEmail.propTypes = {};\n\nexport default VerifyEmail;\n","import styled from 'styled-components';\nimport { Layout } from 'antd';\n\nexport const Container = styled.div`\n display: flex;\n position: relative;\n height: ${props => (props?.mainPage ? '90vh' : 'auto')};\n overflow: hidden auto;\n\n &::-webkit-scrollbar {\n width: 10px;\n margin-right: 15px;\n }\n\n &::-webkit-scrollbar-thumb {\n background: #cccccc;\n border-radius: 10px;\n margin-right: 15px;\n border: 2px solid #f9f9f9;\n }\n`;\n\nexport const CenteredMessage = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n font-size: 16px;\n font-weight: bold;\n margin-top: 45vh;\n`;\n\nexport const StyledLayoutSider = styled(Layout.Sider)``;\n","import { userSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst useDashboard = () => {\n const user = useSelector(userSelector);\n\n return { user };\n};\n\nexport default useDashboard;\n","import ROUTES from 'constants/routes';\nimport Users from '../Users';\nimport GlobalVars from '../GlobalVars';\nimport ActivityLogs from 'components/ActivityLogs';\nimport Versions from '../Versions';\n\nexport const dashboardRoutes = [\n {\n name: 'User List',\n alias: 'Users',\n path: ROUTES.ADMIN_USERS,\n component: Users,\n key: 'admin_users',\n },\n {\n name: 'Global Vars',\n alias: 'Vars',\n path: ROUTES.ADMIN_GLOBAL_VARS,\n component: GlobalVars,\n key: 'global_vars',\n },\n {\n name: 'Admin Bot',\n alias: 'Bots',\n path: ROUTES.BOTS_PAGE,\n key: 'bots',\n },\n {\n name: 'All Users Activity Logs',\n alias: 'Logs',\n path: ROUTES.ADMIN_ACTIVITY_LOGS,\n component: ActivityLogs,\n key: 'activity_logs',\n },\n {\n name: 'Version Management',\n alias: 'Versions',\n path: ROUTES.ADMIN_VERSIONS,\n component: Versions,\n key: 'versions',\n },\n];\n","import styled from 'styled-components';\nimport { Link } from 'react-router-dom';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledDashboardItem = styled.div`\n width: 100%;\n height: 193px;\n background-color: #fff;\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n box-shadow: ${cssVariables.cardShadow};\n border-radius: 5px;\n align-items: inherit;\n border-bottom: none;\n padding: 0px;\n\n :hover {\n border-bottom: none;\n box-shadow: ${cssVariables.itemListShadow};\n cursor: auto;\n }\n`;\n\nexport const StyledInfoWrapper = styled.div`\n display: flex;\n flex-direction: column;\n padding: 3rem 1rem 0 1rem;\n width: auto;\n`;\n\nexport const StyledInfo = styled.div`\n display: flex;\n flex-direction: column;\n width: 70%;\n`;\n\nexport const StyledName = styled.span`\n font-size: 1rem;\n font-weight: 600;\n line-height: 22px;\n color: ${cssVariables.primaryColor};\n overflow: hidden;\n text-overflow: ellipsis;\n text-align: center;\n`;\n\nexport const StyledDescription = styled.span`\n font-size: 13px;\n line-height: 23px;\n color: #afafaf;\n`;\n\nexport const StyledLinkButton = styled(Link)`\n border-radius: 5px;\n align-items: center;\n justify-content: center;\n text-align: center;\n width: 100%;\n height: 46px;\n border: none;\n background-color: #f3f3f3;\n color: black;\n padding: 0.5rem 0 0 0;\n font-size: 1rem;\n\n span {\n font-weight: 400;\n }\n\n .ant-btn-loading,\n :hover,\n :focus,\n :active {\n background-color: rgba(33, 35, 38, 0.774);\n color: white;\n }\n`;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport {\n StyledDashboardItem,\n StyledInfoWrapper,\n StyledInfo,\n StyledName,\n StyledDescription,\n StyledLinkButton,\n} from './DashboardItem.style';\n\nconst DashboardItem = ({ name, alias, link }) => {\n return (\n \n \n {name} \n \n \n \n View {alias} \n \n \n );\n};\n\nDashboardItem.propTypes = {\n name: PropTypes.string,\n alias: PropTypes.string,\n link: PropTypes.string,\n};\n\nexport default DashboardItem;\n","import styled from 'styled-components';\n\nexport const StyledRoot = styled.div`\n padding: 1% 5%;\n position: relative;\n width: 100%;\n\n & .MuiPaper-root {\n & .anticon {\n svg {\n width: 25px;\n height: 25px;\n }\n }\n }\n`;\n\nexport const StyledTitleWrapper = styled.div`\n padding-bottom: 3rem;\n`;\n\nexport const StyledDashboardMenu = styled.div`\n display: flex;\n flex-wrap: wrap;\n align-content: flex-start;\n gap: 20px;\n\n & .MuiPaper-root,\n > div {\n display: flex;\n width: 250px;\n max-width: 300px;\n }\n`;\n","import React from 'react';\nimport Title from 'components/Title';\nimport useDashboard from './hooks';\nimport { dashboardRoutes } from './dashboardRoutes';\nimport DashboardItem from './DashboardItem/DashboardItem';\nimport {\n StyledRoot,\n StyledTitleWrapper,\n StyledDashboardMenu,\n} from './Dashboard.styles';\nimport { Col, Row } from 'antd';\n\nconst Dashboard = () => {\n const { user } = useDashboard();\n\n return (\n \n \n \n \n \n {dashboardRoutes.map(link => {\n return (\n \n \n \n );\n })}\n
\n \n );\n};\n\nexport default Dashboard;\n","import Users from './Users';\nimport Dashboard from './Dashboard';\nimport ROUTES from 'constants/routes';\nimport GlobalVars from './GlobalVars';\nimport ActivityLogs from 'components/ActivityLogs';\nimport Versions from './Versions';\n\nexport const adminSidebarRoutes = [\n {\n name: 'Main Page',\n path: ROUTES.ADMIN_PAGE,\n component: Dashboard,\n key: 'admin',\n },\n {\n name: 'Users List',\n path: ROUTES.ADMIN_USERS,\n component: Users,\n key: 'admin_users',\n },\n {\n name: 'Global Vars',\n path: ROUTES.ADMIN_GLOBAL_VARS,\n component: GlobalVars,\n key: 'global_vars',\n },\n\n {\n name: 'All Users Activity Logs',\n path: ROUTES.ADMIN_ACTIVITY_LOGS,\n component: ActivityLogs,\n key: 'activity_logs',\n },\n {\n name: 'Version Management',\n path: ROUTES.ADMIN_VERSIONS,\n component: Versions,\n key: 'versions',\n },\n];\n","import React, { useMemo } from 'react';\nimport { Route, Link, useLocation } from 'react-router-dom';\nimport { isEqual } from 'lodash';\n\nimport { Container, CenteredMessage } from './Admin.style';\nimport { adminSidebarRoutes } from './adminSidebarRoutes';\nimport ROUTES from 'constants/routes';\nimport Layout from 'components/Layout';\nimport useSelector from 'store/useSelector';\nimport { isAdminSelector } from 'selectors/admin';\nimport { userSelector } from 'selectors/user';\nimport { initialUserState } from 'store/initialState';\n\nconst AdminPage = () => {\n const location = useLocation();\n const isMainPage = useMemo(\n () =>\n adminSidebarRoutes.find(route => location.pathname === route.path)\n ?.key === 'admin',\n [location.pathname, adminSidebarRoutes]\n );\n const isAdmin = useSelector(isAdminSelector);\n const user = useSelector(userSelector);\n\n if (!isAdmin && !isEqual(user, initialUserState)) {\n return (\n \n \n You are not allowed to access this page. \n Back to bots page.\n \n \n );\n }\n\n return (\n \n \n {adminSidebarRoutes.map(routes => {\n return (\n \n );\n })}\n \n \n );\n};\n\nexport default AdminPage;\n","import { useState } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { apiService } from 'services/api.service';\nimport ROUTES from 'constants/routes';\nimport { DEFAULT_ERROR_MESSAGE } from 'constants/error';\nimport { isExternalPageSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst useResetPassword = () => {\n const [password, setPassword] = useState('');\n const [confirmPassword, setConfirmPassword] = useState('');\n const [error, setError] = useState();\n const [loading, setLoading] = useState(false);\n const history = useHistory();\n const isExternalPage = useSelector(isExternalPageSelector);\n const { token } = history.location.state;\n\n const onChangePassword = e => {\n setPassword(e.target.value);\n };\n\n const onChangeConfirmPassword = e => {\n setConfirmPassword(e.target.value);\n };\n\n const hasErrors = () => {\n const hasEnoughCharacters = password.length >= 8;\n\n if (password !== confirmPassword) {\n setError({ error: 'Password did not match.' });\n return true;\n }\n\n if (!hasEnoughCharacters) {\n setError({ error: 'Password did not meet the required characters.' });\n return true;\n }\n\n return false;\n };\n\n const onSubmit = async evt => {\n evt.preventDefault();\n setLoading(true);\n setError(null);\n\n if (hasErrors()) {\n setLoading(false);\n return;\n }\n\n try {\n const res = await apiService.confirmPassword(password, token);\n if (res && res.status === 200) {\n setLoading(false);\n history.push({\n pathname: isExternalPage ? ROUTES.EXTERNAL_LOGIN : ROUTES.LOGIN,\n state: { success: true, action: 'update' },\n });\n setPassword('');\n setConfirmPassword('');\n setError(null);\n }\n } catch (err) {\n setLoading(false);\n setConfirmPassword('');\n return setError(err.message || DEFAULT_ERROR_MESSAGE);\n }\n return setLoading(false);\n };\n\n return {\n error,\n loading,\n onChangePassword,\n onChangeConfirmPassword,\n onSubmit,\n };\n};\n\nexport default useResetPassword;\n","import styled from 'styled-components';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledResetPassword = styled.div`\n display: block;\n align-items: flex-start;\n margin: 0 auto;\n padding-top: 120px;\n flex-direction: column;\n max-width: 472px;\n`;\n\nexport const StyledHeaderTitle = styled.div`\n margin-bottom: 40px;\n`;\n\nexport const StyledResetPasswordFields = styled.div`\n display: flex;\n flex-direction: column;\n row-gap: 20px;\n\n &label {\n font-weight: ${cssVariables.font.normal};\n }\n`;\n\nexport const StyledForm = styled.form`\n width: 100%;\n padding: 0 10px;\n margin-top: 24px;\n display: flex;\n flex-direction: column;\n row-gap: 20px;\n`;\n","import React from 'react';\nimport { Typography } from 'antd';\nimport Input from 'components/Input';\nimport useResetPassword from './hooks';\nimport Alert from 'components/Alert';\nimport Button from 'components/Button';\nimport {\n StyledResetPassword,\n StyledHeaderTitle,\n StyledResetPasswordFields,\n StyledForm,\n} from './ConfirmResetPassword.style';\n\nconst { Title, Text } = Typography;\n\nconst ConfirmResetPassword = () => {\n const {\n error,\n loading,\n onChangePassword,\n onChangeConfirmPassword,\n onSubmit,\n } = useResetPassword();\n\n return (\n \n \n Reset Password \n New password must be 8 character or more. \n \n \n {error && }\n \n \n \n \n \n \n \n );\n};\n\nexport default ConfirmResetPassword;\n","import { createSelector } from 'reselect';\nimport { isEqual } from 'lodash';\nimport { initialWSState } from 'store/initialState';\n\nexport const websocketSelector = state => state.websocket;\n\nexport const isInitialWebsocketStateSelector = createSelector(\n websocketSelector,\n websocket => {\n return isEqual(websocket, initialWSState);\n }\n);\n","import React, { useContext, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { Route, Redirect, useLocation } from 'react-router-dom';\nimport useWebSocket, { ReadyState } from 'react-use-websocket';\n\nimport { BOT_REDIRECT } from 'constants/localStorage';\nimport ROUTES from 'constants/routes';\nimport { Context } from 'store/store';\nimport { getTokenSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\nimport {\n DISCONNECT_WEBSOCKET,\n SET_WS_ASK_QUESTION_ACTION,\n SET_WS_IMPORTER_PROGRESS_COUNT,\n SET_WS_SCRAPER_ACTION,\n SET_WS_TOKEN,\n UPDATE_BOT_SYNC,\n WS_ADD_FILE,\n} from 'store/action';\nimport { RECONNECT_INTERVAL_IN_MS, SOCKET_URL } from 'constants/websocket';\nimport { isInitialWebsocketStateSelector } from 'selectors/websocket';\n\nconst PrivateRoute = props => {\n const { component: Component, ...rest } = props;\n const [state, dispatch] = useContext(Context);\n const {\n isAuthenticated,\n bot: { strippedBotID, jid },\n webSocket,\n } = state;\n const token = useSelector(getTokenSelector);\n const isInitialWebsocketState = useSelector(isInitialWebsocketStateSelector);\n const { pathname } = useLocation();\n const { sendJsonMessage, lastMessage, readyState } = useWebSocket(\n SOCKET_URL,\n {\n shouldReconnect: closeEvent => true,\n retryOnError: () => true,\n reconnectInterval: RECONNECT_INTERVAL_IN_MS,\n onError: e => {\n if (!isInitialWebsocketState) {\n dispatch({\n type: DISCONNECT_WEBSOCKET,\n });\n }\n },\n onClose: e => {\n if (!isInitialWebsocketState) {\n dispatch({\n type: DISCONNECT_WEBSOCKET,\n });\n }\n },\n }\n );\n\n const nextLocation = localStorage.getItem(BOT_REDIRECT);\n const pathBotJID = pathname.split('/bot/').pop();\n\n // Rename readyState from number to human readable word\n const connectionStatus = {\n [ReadyState.CONNECTING]: 'Connecting',\n [ReadyState.OPEN]: 'Open',\n [ReadyState.CLOSING]: 'Closing',\n [ReadyState.CLOSED]: 'Closed',\n [ReadyState.UNINSTANTIATED]: 'Uninstantiated',\n }[readyState];\n\n useEffect(() => {\n const clientConnectPayload = {\n type: 'client_connect',\n data: {\n token,\n },\n };\n\n switch (connectionStatus) {\n case 'Open':\n if (!webSocket?.channel && token) {\n sendJsonMessage(clientConnectPayload);\n }\n break;\n\n default:\n break;\n }\n }, [connectionStatus, webSocket?.channel, token]);\n\n useEffect(() => {\n if (lastMessage?.data) {\n const wsData = JSON.parse(lastMessage.data);\n switch (wsData.type) {\n case 'client_connected':\n if (!webSocket?.channel) {\n dispatch({\n type: SET_WS_TOKEN,\n payload: wsData.data,\n });\n }\n break;\n\n case 'ask_question':\n dispatch({\n type: SET_WS_ASK_QUESTION_ACTION,\n payload: wsData.data,\n });\n break;\n\n case 'scraper':\n dispatch({\n type: SET_WS_SCRAPER_ACTION,\n payload: wsData.data,\n });\n break;\n\n case 'add_file':\n dispatch({\n type: WS_ADD_FILE,\n payload: wsData.data?.answer?.success,\n });\n break;\n\n case 'sync_bot':\n dispatch({\n type: UPDATE_BOT_SYNC,\n payload: {\n jid,\n },\n });\n break;\n\n case 'importer':\n dispatch({\n type: SET_WS_IMPORTER_PROGRESS_COUNT,\n payload: wsData.data,\n });\n break;\n\n default:\n break;\n }\n }\n }, [lastMessage, webSocket?.channel]);\n\n if (!nextLocation && !!pathBotJID && pathname.includes('/bot/') && !token) {\n const strippedURL = window.location.href.replace(\n // remove if uuid prefix is present\n /urn:uuid:.*&/,\n strippedBotID + '&'\n );\n localStorage.setItem(BOT_REDIRECT, strippedURL);\n }\n\n return (\n \n isAuthenticated === true ? (\n \n ) : (\n \n )\n }\n />\n );\n};\n\nPrivateRoute.propTypes = {\n component: PropTypes.any,\n};\n\nexport default PrivateRoute;\n","import React, { useContext } from 'react';\nimport PropTypes from 'prop-types';\nimport { Route, Redirect } from 'react-router-dom';\n\nimport { Context } from 'store/store';\n\nconst AdminRoute = props => {\n const [state] = useContext(Context);\n const { isAuthenticated } = state;\n const { component: Component, location, ...rest } = props;\n\n return (\n \n isAuthenticated === true ? (\n \n ) : (\n \n )\n }\n />\n );\n};\n\nAdminRoute.propTypes = {\n location: PropTypes.object,\n component: PropTypes.any,\n};\n\nexport default AdminRoute;\n","import React, { useState, useEffect, useContext } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { useIdleTimer } from 'react-idle-timer';\nimport { Context } from 'store/store';\nimport Modal from 'components/Modals/GenericModal';\nimport ROUTES from 'constants/routes';\nimport { LOGOUT_EXTERNAL_USER, LOGOUT_USER } from 'store/action';\nimport { isExternalPageSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst IdleTimerCountdown = () => {\n const [isUserIdle, setUserAsIdle] = useState(false);\n const [countdown, setCountdown] = useState(60 * 5); // 5 minutes warning\n const isExternalPage = useSelector(isExternalPageSelector);\n const [, dispatch] = useContext(Context);\n const history = useHistory();\n\n const handleOnIdle = () => {\n setUserAsIdle(true);\n };\n\n useEffect(() => {\n if (isUserIdle) {\n const countdownTimer = setInterval(() => {\n setCountdown(countdown => countdown - 1);\n }, 1000);\n\n return () => clearInterval(countdownTimer);\n }\n }, [isUserIdle]);\n\n useEffect(() => {\n if (countdown === 0) {\n dispatch({\n type: isExternalPage ? LOGOUT_EXTERNAL_USER : LOGOUT_USER,\n });\n history.push(ROUTES.LOGOUT);\n }\n }, [countdown, dispatch, history]);\n\n const onOkButtonClick = () => {\n setUserAsIdle(false);\n setCountdown(60 * 5);\n reset();\n };\n\n const { reset } = useIdleTimer({\n timeout: 1000 * 60 * 10, // ten minutes idle limit\n onIdle: handleOnIdle,\n debounce: 500,\n crossTab: true,\n });\n\n const seconds = countdown % 60;\n const minutes = Math.floor(countdown / 60);\n\n return (\n \n You've been inactive for 10 minutes.
\n Logging you out in {`${minutes}:${seconds}`}
\n \n );\n};\n\nexport default IdleTimerCountdown;\n","import React, { useEffect, useContext } from 'react';\nimport styled from 'styled-components';\nimport { useHistory } from 'react-router-dom';\n\nimport DeadBotLogo from 'assets/images/zsb-dead-logo.png';\nimport { StyledLogo } from 'pages/Auth/SignIn/SignIn.style';\nimport Button from 'components/Button';\nimport { cssVariables } from 'styles/root';\nimport ROUTES from 'constants/routes';\nimport { Context } from 'store/store';\nimport { LOGOUT_EXTERNAL_USER, LOGOUT_USER } from 'store/action';\nimport { isExternalPageSelector } from 'selectors/user';\nimport useSelector from 'store/useSelector';\n\nconst StyledInfo = styled.div`\n align-items: center;\n display: flex;\n justify-content: center;\n background-color: ${cssVariables.blue1};\n margin: 20px 0;\n font-size: 1.2rem;\n color: ${cssVariables.primaryGray};\n padding: 1%;\n`;\n\nconst StyledLogout = styled.div`\n padding-top: 5%;\n display: flex;\n justify-content: center;\n flex-direction: column;\n align-items: center;\n width: 100%;\n\n button {\n margin-top: 24px;\n }\n`;\n\nconst Logout = () => {\n const history = useHistory();\n const [, dispatch] = useContext(Context);\n const isExternalPage = useSelector(isExternalPageSelector);\n\n useEffect(() => {\n dispatch({\n type: isExternalPage ? LOGOUT_EXTERNAL_USER : LOGOUT_USER,\n payload: null,\n });\n }, []);\n\n return (\n \n \n {'You have logged out.'} \n \n history.push(isExternalPage ? ROUTES.EXTERNAL_LOGIN : ROUTES.LOGIN)\n }\n />\n \n );\n};\n\nexport default Logout;\n","import React, { useContext, useEffect, useRef } from 'react';\nimport { Route, Switch, BrowserRouter as Router } from 'react-router-dom';\nimport { createBrowserHistory } from 'history';\nimport { ThemeProvider } from 'styled-components';\n\nimport { Context } from 'store/store';\nimport Auth from 'pages/Auth';\nimport Bots from 'pages/Bots';\nimport BotDetails from 'pages/BotDetails';\nimport UserProfile from 'pages/UserProfile';\nimport VerifyEmail from 'pages/VerifyEmail';\nimport Admin from 'pages/Admin';\nimport ConfirmResetPassword from 'pages/ConfirmResetPassword';\nimport PrivateRoute from 'components/PrivateRoute';\nimport AdminRoute from 'components/AdminRoute';\nimport IdleTimerCountdown from 'components/IdleTimerCountdown';\nimport ROUTES from 'constants/routes';\nimport { setToken } from 'services/auth.service';\nimport { cssVariables } from 'styles/root';\nimport Logout from 'pages/Logout';\nimport './App.css';\n\nconst useUnload = fn => {\n const cb = useRef(fn);\n\n useEffect(() => {\n const onUnload = cb.current;\n window.addEventListener('beforeunload', onUnload);\n return () => {\n window.removeEventListener('beforeunload', onUnload);\n };\n }, [cb]);\n};\n\nconst App = () => {\n const [state] = useContext(Context);\n const history = createBrowserHistory();\n\n const { isAuthenticated, token } = state;\n\n useUnload(e => {\n setToken(token, 1000 * 60 * 60 * 2);\n });\n\n return (\n \n \n
\n {isAuthenticated && }\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n );\n};\n\nexport default App;\n","const reportWebVitals = onPerfEntry => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport 'antd/dist/antd.css';\nimport 'react-chat-widget/lib/styles.css';\nimport './index.css';\nimport App from './App';\nimport Store from 'store/store';\nimport reportWebVitals from './reportWebVitals';\nimport FallbackBoundary from 'components/FallbackBoundary';\n\nReactDOM.render(\n // Antd uses findDOMNode and throws error on dev's browser console\n // TODO: migrate to latest antd component to enable StrictMode\n // \n \n \n \n \n ,\n // ,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n","import ROUTES from './routes';\n\nexport const ZSB_PRICING_PAGE = ROUTES.SUBSCRIPTION;\n\nexport const ZSB_CONTACT = 'https://www.zeroshotbot.com/contact';\n","import { Button } from 'antd';\nimport styled from 'styled-components';\nimport { cssVariables } from 'styles/root';\n\nexport const StyledButton = styled(Button)`\n border-radius: ${props => (props.rounded ? '50px' : '5px')};\n align-items: center;\n justify-content: center;\n width: ${props => (props.$full ? '100%' : 'auto')};\n border: ${props => (props.bordered ? `1px solid ${props.color}` : 'none')};\n box-shadow: none;\n background-color: ${props =>\n props.bordered || props.variant === 'link'\n ? 'transparent'\n : props.color} !important;\n color: ${props =>\n props.disabled\n ? cssVariables.gray1\n : props.bordered || props.variant === 'link'\n ? props.color || cssVariables.primaryBlueHover\n : props.variant.includes('secondary') || props.variant === 'white'\n ? cssVariables.primaryColor\n : '#fff'} !important;\n .ant-btn-loading,\n :hover,\n :focus,\n :active {\n background-color: ${props =>\n props.variant !== 'link' ? props.hover : cssVariables.blue1} !important;\n color: ${props =>\n props.variant !== 'link' && props.variant !== 'error'\n ? props.color\n : props.variant === 'error'\n ? '#fff'\n : cssVariables.primaryBlue} !important;\n }\n`;\n","import React from 'react';\nimport PropTypes from 'prop-types';\nimport { cssVariables } from 'styles/root';\nimport { StyledButton } from './Button.style';\n\nconst Button = props => {\n const {\n className,\n disabled,\n endIcon,\n full,\n size,\n startIcon,\n type,\n variant,\n value,\n ...rest\n } = props;\n const getColor = variant => {\n switch (variant) {\n case 'primary':\n case 'primary-btn-v2':\n case 'link':\n return cssVariables.primaryBlue;\n case 'secondary':\n case 'secondary-btn-v2':\n return cssVariables.secondaryColor;\n case 'success':\n return cssVariables.success;\n case 'cyan':\n return cssVariables.primaryCyan;\n case 'error':\n return cssVariables.errorButtonColor;\n case 'white':\n return '#fff';\n case 'red':\n return cssVariables.red10;\n case 'gray':\n case 'grey':\n return cssVariables.gray0;\n default:\n return cssVariables.primaryBlue;\n }\n };\n\n const getHoverColor = variant => {\n switch (variant) {\n case 'primary':\n case 'primary-btn-v2':\n return cssVariables.primaryBlueHover;\n case 'secondary':\n case 'secondary-btn-v2':\n return cssVariables.gray1;\n case 'success':\n return cssVariables.successHover;\n case 'error':\n return cssVariables.errorBorder;\n case 'white':\n case 'link':\n return cssVariables.primaryTransparent;\n case 'red':\n return cssVariables.red1;\n case 'gray':\n case 'grey':\n return cssVariables.secondaryColor;\n default:\n return cssVariables.primaryBlueHover;\n }\n };\n\n return (\n \n {startIcon || null}\n {value ? {value} : rest.children}\n {endIcon || null}\n \n );\n};\n\nButton.defaultProps = {\n size: 'large',\n variant: 'primary-btn-v2',\n};\n\nButton.propTypes = {\n size: PropTypes.oneOf(['small', 'medium', 'middle', 'large']),\n value: PropTypes.string,\n variant: PropTypes.oneOf([\n 'primary',\n 'secondary',\n 'link',\n 'primary-btn-v2',\n 'secondary-btn-v2',\n 'success',\n 'error',\n 'gray',\n 'grey',\n 'white',\n 'cyan',\n ]),\n className: PropTypes.string,\n full: PropTypes.bool,\n disabled: PropTypes.bool,\n type: PropTypes.string,\n startIcon: PropTypes.node,\n endIcon: PropTypes.node,\n};\n\nexport { Button };\n","import { Button } from './Button';\n\nexport default Button;\n","import axios from 'axios';\nimport { DEFAULT_ANSWER_VERSION } from 'constants/answerbank/defaults';\nimport { DEFAULT_ERROR_MESSAGE, GET_DATA_ERROR } from 'constants/error';\nimport { createDisplayAnswerAPIPayload } from 'utils/answers';\nimport { convertToNewQuickReplyPayload } from 'utils/answers';\nimport { isAnObject, isZSBUUID } from 'utils/dataTypes';\nimport { convertToEndOfDay, getLastWeekISODate } from 'utils/dates';\nimport { snakeCase } from 'lodash';\nimport { renderPreconfigData } from 'store/reducers/helpers/bot/answers';\n\nconst API_BASE_URL = process.env.REACT_APP_API_BASE_URL;\nconst STRIPE_URL = process.env.REACT_APP_STRIPE_URL;\nconst WEBHOOK_MESSENGER = process.env.REACT_APP_WEBHOOK_MESSENGER;\nconst WEBHOOK_VIBER = process.env.REACT_APP_WEBHOOK_URL_VIBER;\nconst CANCEL_REQUEST = 'Operation was cancelled by the user.';\n\nexport const getErrorMessage = error => {\n if (error?.response?.data && isAnObject(error?.response?.data)) {\n const isGeneric =\n Object.values(error.response.data)[0]?.includes(\n 'This field is required.'\n ) || false;\n const message = Object.values(error.response.data)[0];\n const key = Object.keys(error.response.data)[0];\n return isGeneric ? `${key.toUpperCase()} is required` : message;\n } else if (\n (error?.response?.status >= 500 && error?.response?.status <= 599) ||\n (error?.response?.status >= 400 && error?.response?.status < 500)\n ) {\n return typeof error.response === 'string' && error.response?.length <= 100\n ? error.response\n : typeof error.response?.data === 'string' &&\n error.response?.data?.length <= 100\n ? error.response?.data\n : DEFAULT_ERROR_MESSAGE;\n }\n return DEFAULT_ERROR_MESSAGE;\n};\n\nexport const apiService = {\n jacLoadApplication: function (tokenArg) {\n axios.defaults.headers.common['Authorization'] = 'token ' + tokenArg;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/alias_list',\n method: 'post',\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n setDefaultSentinel: function (token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/sentinel_active_global',\n method: 'post',\n })\n .then(res => {\n if (\n !res?.data ||\n !res?.data?.success ||\n res?.data?.response?.includes('No global sentinel is available')\n ) {\n throw new Error('No global sentinel is available');\n }\n return res;\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n createGraph: function (token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/graph_create',\n method: 'post',\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n userGET: function (url, token) {\n if (!url.includes(`user/activate/`)) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n }\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: `/${url}`,\n method: 'get',\n data: url.includes(`user/activate/`) ? { code: token } : { token },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n userPOST: function (url, data, token) {\n if (token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n } else if (url.includes('token')) {\n delete axios.defaults.headers.common['Authorization'];\n }\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: `/${url}`,\n method: 'post',\n data,\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n jacSyncSentinelGlobal: function (data, tokenArg) {\n axios.defaults.headers.common['Authorization'] = 'token ' + tokenArg;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/walker_run',\n method: 'post',\n data: { ...data },\n })\n .then(response => {\n if (!response.data.success) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n return response;\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n // Pass token when calling `jacPrimeRun`\n jacPrimeRun: function (data, sentinel, tokenArg) {\n const cancelTokenSource = axios.CancelToken.source();\n axios.defaults.headers.common['Authorization'] = 'token ' + tokenArg;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: `/js/walker_run?name=${data.name}`,\n cancelToken: cancelTokenSource.token,\n method: 'post',\n data: {\n ...data,\n snt: sentinel || 'active:sentinel',\n nd: data.nd || 'active:graph',\n },\n })\n .then(response => {\n if (!response.data.success && !response.data.is_queued) {\n throw new Error(DEFAULT_ERROR_MESSAGE);\n }\n return response;\n })\n .catch(error => {\n if (axios.isCancel(error)) {\n throw new Error(CANCEL_REQUEST);\n }\n throw new Error(getErrorMessage(error));\n });\n },\n\n jacPrimeRunMultipart: function (data, sentinel, graph, tokenArg) {\n axios.defaults.headers.common['Authorization'] = 'token ' + tokenArg;\n let formData = new FormData();\n formData.append('snt', sentinel);\n formData.append('nd', graph);\n formData.append('name', data.name);\n\n Object.entries(data.ctx).forEach((item, key) => {\n // Find the files from the `data.ctx`\n // file object will have an integer key name\n if (item[0] && !isNaN(Number.parseInt(item[0]))) {\n // And append individually\n formData.append('files', item[1], item[1].name);\n }\n });\n\n // removing the object that has a key name equivalent to a number/integer\n // to exclude the files from the ctx before we append to formData\n const dataCtxArr = Object.entries(data.ctx).filter(\n ([key, value]) => !!key && isNaN(Number.parseInt(key))\n );\n\n // append the filtered ctx without the files\n formData.append('ctx', JSON.stringify(Object.fromEntries(dataCtxArr)));\n\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/walker_run',\n method: 'post',\n data: formData,\n })\n .then(response => {\n // if (!response.data.success) {\n // throw new Error('Something went wrong while doing that');\n // }\n return response;\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n stripePOST: function (url, data, tokenArg) {\n return axios\n .request({\n baseURL: STRIPE_URL,\n url: `/${url}`,\n method: 'post',\n data: { ...data },\n })\n .catch(error => {\n console.log('error ', error);\n return error.response;\n });\n },\n\n stripeGET: function (url, tokenArg) {\n return axios\n .request({\n baseURL: STRIPE_URL,\n url: `/${url}`,\n method: 'get',\n })\n .catch(error => {\n console.log('error ', error);\n return error.response;\n });\n },\n\n spawnCreate: function (name, token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios.request({\n baseURL: API_BASE_URL,\n url: '/js/walker_spawn_create',\n method: 'post',\n data: {\n name,\n },\n });\n },\n\n getPublicKey: function (token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios.request({\n baseURL: API_BASE_URL,\n url: '/js/walker_get',\n method: 'post',\n data: {\n mode: 'keys',\n wlk: 'spawned:walker:zsb_public_api',\n detailed: false,\n },\n });\n },\n\n getGraphObject: function (jid, token, depth = 0) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios.request({\n baseURL: API_BASE_URL,\n url: '/js/object_get',\n method: 'post',\n data: {\n obj: jid,\n depth,\n detailed: true,\n },\n });\n },\n\n getGlobalVars: function (jid, token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios.request({\n baseURL: API_BASE_URL,\n url: '/global',\n method: 'get',\n });\n },\n\n getVersions: function (sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_global_features',\n snt: 'active:sentinel',\n nd: 'active:graph',\n },\n sentinel,\n token\n );\n },\n\n addVersions: function (sentinel, token, ctx) {\n return this.jacPrimeRun(\n {\n name: 'add_version',\n ctx,\n snt: 'active:sentinel',\n nd: 'active:graph',\n },\n sentinel,\n token\n );\n },\n\n updateUserVersion: function (sentinel, token, ctx, graph) {\n return this.jacPrimeRun(\n {\n name: 'set_current_version',\n ctx,\n nd: graph,\n },\n sentinel,\n token\n );\n },\n\n updateGlobalVars: function (ctx = {}) {\n return this.jacPrimeRun({\n name: 'global_var_setter',\n ctx,\n });\n },\n\n addGlobalVars: function (name, value, token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios.request({\n baseURL: API_BASE_URL,\n url: '/js_admin/global_set',\n method: 'post',\n data: {\n name,\n value,\n },\n });\n },\n\n init: function (sentinel, graph, token) {\n return this.jacPrimeRun(\n {\n name: 'init',\n nd: graph,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n getBotDetails: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_bot',\n nd: jid,\n ctx: {\n sync_count: true,\n start_date: getLastWeekISODate(),\n },\n },\n sentinel,\n token\n );\n },\n\n getBots: function (sentinel, graph, token) {\n return this.jacPrimeRun(\n {\n name: 'get_bots',\n nd: graph,\n ctx: {\n start_date: getLastWeekISODate(),\n },\n },\n sentinel,\n token\n );\n },\n\n addBot: function (sentinel, graph, newBot, token) {\n return this.jacPrimeRun(\n {\n name: 'add_bot',\n nd: graph,\n ctx: { ...newBot },\n },\n sentinel,\n token\n );\n },\n\n getBotSummary: function (sentinel, graph, token) {\n return this.jacPrimeRun(\n {\n name: 'get_bot_summary',\n nd: graph,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n getAnswers: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_answers',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n getAnswer: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_answer ',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n createAnswer: function (\n sentinel,\n jid,\n source,\n answerDetails,\n token,\n skip_similarity_check,\n version = DEFAULT_ANSWER_VERSION,\n editor,\n categoryId\n ) {\n const { text, displayAnswer } = answerDetails;\n const payloadDisplayAnswer = createDisplayAnswerAPIPayload(displayAnswer);\n return this.jacPrimeRun(\n {\n name: 'create_answer',\n nd: jid,\n ctx: {\n text,\n ...payloadDisplayAnswer,\n editor,\n category_id: categoryId,\n ans_version: version,\n skip_similarity_check,\n ...source,\n },\n },\n sentinel,\n token\n );\n },\n\n createManyAnswers: function (sentinel, jid, source, input, token) {\n return this.jacPrimeRun(\n {\n name: 'create_many_answers',\n nd: jid,\n ctx: { entry_list: input, ...source },\n },\n sentinel,\n token\n );\n },\n\n importAnswerBank: function (sentinel, jid, source, answerbank, token) {\n return this.jacPrimeRun(\n {\n name: 'import_answerbank',\n nd: jid,\n ctx: { answerbank, ans_version: 'final', ...source },\n },\n sentinel,\n token\n );\n },\n\n importDraftAnswerBank: function (sentinel, jid, source, answerbank, token) {\n return this.jacPrimeRun(\n {\n name: 'import_answerbank',\n nd: jid,\n ctx: { answerbank, ans_version: 'draft', ...source },\n },\n sentinel,\n token\n );\n },\n\n getSummary: function (sentinel, jid, source, type, maxCount, token) {\n return this.jacPrimeRun(\n {\n name: 'text_summarization',\n nd: jid,\n ctx: { doc_type: type, [type]: source, sentence_count: maxCount },\n },\n sentinel,\n token\n );\n },\n\n getOpenAiTemplate: function (sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_openai_template',\n },\n sentinel,\n token\n );\n },\n\n getOpenAiFAQTemplate: function (sentinel, token, ctx) {\n return this.jacPrimeRun(\n {\n name: 'get_openai_template',\n ctx,\n },\n sentinel,\n token\n );\n },\n\n generateFAQViaOpenAi: function (sentinel, token, generateFAQObject) {\n return this.jacPrimeRun(\n {\n name: 'generate_answers_via_openai',\n ctx: {\n src: 'openai_faq_template',\n fields: {\n ...generateFAQObject,\n },\n },\n snt: 'active:sentinel',\n },\n sentinel,\n token\n );\n },\n\n generateAanswersViaOpenAI: function (\n sentinel,\n jid,\n token,\n textGenerateObject\n ) {\n return this.jacPrimeRun(\n {\n name: 'generate_answers_via_openai',\n nd: jid,\n ctx: {\n pre_text: '',\n fields: {\n ...textGenerateObject,\n keywords: textGenerateObject.keywords\n ? textGenerateObject.keywords.join()\n : '',\n count: textGenerateObject.count || 5,\n },\n post_text: '',\n },\n },\n sentinel,\n token\n );\n },\n\n changeAnswer: function (\n sentinel,\n jid,\n input,\n displayAnswer,\n categoryId,\n token,\n editor,\n quickButtons,\n version\n ) {\n const payloadDisplayAnswer = createDisplayAnswerAPIPayload(displayAnswer);\n const quickReply =\n quickButtons && quickButtons?.quickReply ? 'true' : 'false';\n const requestAgent =\n quickButtons && quickButtons?.requestAgent ? 'true' : 'false';\n\n return this.jacPrimeRun(\n {\n name: 'change_answer',\n nd: jid,\n ctx: {\n ...payloadDisplayAnswer,\n text: input,\n editor,\n category_id: categoryId,\n quick_reply: quickButtons\n ? convertToNewQuickReplyPayload(quickButtons)\n : null,\n callback: requestAgent,\n ans_version: version,\n },\n },\n sentinel,\n token\n );\n },\n\n deleteAnswer: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_answer',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n askQuestion: function (\n sentinel,\n jid,\n input,\n token,\n channel,\n useDraft,\n history,\n webSocketChannel,\n interactionId\n ) {\n const isDemo = channel?.toLowerCase()?.includes('demo');\n const isFromSidebarChat = channel?.toLowerCase()?.includes('zsb platform');\n const integration = {\n id: isDemo ? '0' : isFromSidebarChat ? '1' : '',\n name: channel,\n };\n return this.jacPrimeRun(\n {\n name: 'ask_question',\n nd: jid,\n ctx: {\n text: input,\n integration,\n metadata: { channel, interaction_id: interactionId },\n use_draft: useDraft,\n history,\n dont_log: false,\n ws_target: webSocketChannel || null,\n },\n is_async: webSocketChannel ? true : false,\n },\n sentinel,\n token\n );\n },\n\n askQuestion_nolog: function (sentinel, jid, input, token) {\n return this.jacPrimeRun(\n {\n name: 'ask_question_nolog',\n nd: jid,\n ctx: { text: input, dont_log: true },\n },\n sentinel,\n token\n );\n },\n\n userToken: function (email, password) {\n const data = { email, password };\n return this.userPOST('user/token/', data);\n },\n\n userCreate: function (email, password, name) {\n const data = {\n email,\n password,\n name,\n is_activated: process.env.NODE_ENV === 'development' ? true : false,\n };\n return this.userPOST('user/create/', data);\n },\n\n userActivate: function (code) {\n return this.userGET(`user/activate/${code}`, code);\n },\n\n getMasterAllUser: function (data, token) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js_admin/master_allusers',\n method: 'post',\n data,\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n masterUpdateUser: function (id, token, data) {\n return this.userPOST(`user/manage/${id}`, data, token);\n },\n\n getGraphData: function (graph, token, depth) {\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/graph_get',\n method: 'post',\n data: {\n nd: graph,\n detailed: false,\n depth: depth || undefined,\n },\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n getUser: function (token) {\n return this.userGET('user/manage/', token);\n },\n\n updateUser: function (user) {\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/user/manage/',\n method: 'patch',\n data: { ...user, is_activated: true },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n updatePassword: function (email) {\n const data = { email };\n return this.userPOST('user/password_reset/', data);\n },\n\n updatePasswordValidateToken: function (token) {\n const data = { token };\n return this.userPOST('user/password_reset/validate_token/', data);\n },\n\n confirmPassword: function (password, token) {\n const data = { password, token };\n return this.userPOST('user/password_reset/confirm/', data);\n },\n\n createStripeCustomer: function (data) {\n return this.stripePOST('customer', data);\n },\n\n createStripeSubscription: function (data) {\n return this.stripePOST('subscription', data);\n },\n\n getStripeSubscription: function (subscription_id) {\n return this.stripeGET(`subscription/${subscription_id}`);\n },\n\n cancelSubscription: function (data) {\n return this.stripePOST('subscription/cancel', data);\n },\n\n updateSubscription: function (data) {\n return this.stripePOST('subscription/update', data);\n },\n\n getStripeCustomer: function (customer_id) {\n return this.stripeGET(`customer/${customer_id}`);\n },\n\n getPaymentMethods: function (customer_id) {\n return this.stripeGET(`customer/payment-method/${customer_id}`);\n },\n\n getCustomerPayments: function (customer_id) {\n return this.stripeGET(`payments/${customer_id}`);\n },\n\n getInvoices: function (subscription_id) {\n return this.stripeGET(`invoice/${subscription_id}`);\n },\n\n setCustomerUsage: function (data) {\n return this.stripePOST('subscription/usage', data);\n },\n\n addPaymentMethod: function (data) {\n return this.stripePOST('customer/add-payment-method', data);\n },\n\n updatePaymentMethod: function (data) {\n return this.stripePOST('customer/update-payment-method', data);\n },\n\n deleteBot: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_bot',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n editBot: function (\n sentinel,\n jid,\n name,\n mode = '',\n desc,\n token,\n metadata,\n mailConfig,\n botLanguageConfig,\n defaultAnswer,\n threshold\n ) {\n const defaultAnswerData =\n typeof defaultAnswer === 'string' ||\n (isAnObject(defaultAnswer) &&\n !defaultAnswer.show_text &&\n !defaultAnswer.show_html)\n ? createDisplayAnswerAPIPayload(defaultAnswer)\n : defaultAnswer;\n return this.jacPrimeRun(\n {\n name: 'change_bot',\n nd: jid,\n ctx: {\n name,\n desc,\n metadata,\n mode,\n mail_config: mailConfig,\n default_answer: {\n ...defaultAnswerData,\n text:\n typeof defaultAnswer === 'string'\n ? defaultAnswer\n : defaultAnswer?.text,\n thresh_score: Number(threshold),\n },\n ...botLanguageConfig,\n },\n },\n sentinel,\n token\n );\n },\n\n editTranslationSettings: function (sentinel, jid, token, translateBot) {\n return this.jacPrimeRun(\n {\n name: 'change_bot',\n nd: jid,\n ctx: {\n auto_translate_response: translateBot,\n },\n },\n sentinel,\n token\n );\n },\n\n linkQuestion: function (sentinel, jid, text_input, token) {\n return this.jacPrimeRun(\n {\n name: 'link_question',\n nd: jid,\n ctx: { text: text_input },\n },\n sentinel,\n token\n );\n },\n\n unlinkQuestion: function (sentinel, jid, idx, token) {\n return this.jacPrimeRun(\n {\n name: 'unlink_question',\n nd: jid,\n ctx: { idx },\n },\n sentinel,\n token\n );\n },\n\n getMatches: function (sentinel, jid, text_input, num_answer_matches, token) {\n return this.jacPrimeRun(\n {\n name: 'get_question_matches',\n nd: jid,\n ctx: { question: text_input, limit: num_answer_matches },\n },\n sentinel,\n token\n );\n },\n\n getAnswerInsights: function (\n answerId,\n startDate,\n endDate,\n sentinel,\n jid,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_log',\n nd: jid,\n ctx: {\n answer_ids: answerId,\n start_date: startDate,\n end_date: endDate,\n integration_ids: [],\n },\n },\n sentinel,\n token\n );\n },\n\n getQuestionsByDateRange: function (\n sentinel,\n jid,\n start_date,\n end_date,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_log_date_filter',\n nd: jid,\n ctx: { start_date, end_date },\n },\n sentinel,\n token\n );\n },\n\n getFeedbackByDateRange: function (\n sentinel,\n jid,\n start_date,\n end_date,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_feedback_date_filter',\n nd: jid,\n ctx: { start_date, end_date },\n },\n sentinel,\n token\n );\n },\n\n getElasticLogAggregations: function (\n sentinel,\n jid,\n start_date,\n end_date,\n token,\n integrationIds,\n categories,\n params\n ) {\n const snakedCaseParams = Object.keys(params).reduce((acc, key) => {\n if (typeof key === 'string') {\n return { ...acc, [snakeCase(key)]: params[key] };\n }\n return key;\n }, {});\n return this.jacPrimeRun(\n {\n name: 'get_log',\n nd: jid,\n ctx: {\n start_date,\n end_date,\n integration_ids: Array.isArray(integrationIds)\n ? integrationIds\n : [integrationIds],\n category_ids: categories || [],\n ...snakedCaseParams,\n answer_ids: snakedCaseParams.answer_ids || [],\n },\n },\n sentinel,\n token\n ).then(response => {\n if (\n !!response.data.report[0] &&\n !!response.data.report[0].status &&\n response.data.report[0].status !== 200 &&\n response.data.report[0].status !== 404 &&\n !params?.for_aggs\n ) {\n throw new Error(GET_DATA_ERROR);\n } else if (response.data.report[0].status === 400) {\n throw new Error(\n 'Sorry! Something went wrong while fetching analytics logs.'\n );\n }\n return response;\n });\n },\n\n getElasticLogDateFilter: function (\n sentinel,\n jid,\n start_date,\n end_date,\n token,\n integrationIds,\n categories,\n params\n ) {\n const snakedCaseParams = Object.keys(params).reduce((acc, key) => {\n if (typeof key === 'string') {\n return { ...acc, [snakeCase(key)]: params[key] };\n }\n return key;\n }, {});\n\n return this.jacPrimeRun(\n {\n name: 'get_log',\n nd: jid,\n ctx: {\n start_date,\n end_date,\n category_ids: categories || [],\n integration_ids: Array.isArray(integrationIds)\n ? integrationIds\n : [integrationIds],\n ...snakedCaseParams,\n answer_ids: snakedCaseParams.answer_ids || [],\n },\n },\n sentinel,\n token\n ).then(response => {\n if (\n !!response.data.report[0] &&\n !!response.data.report[0].status &&\n response.data.report[0].status !== 200 &&\n response.data.report[0].status !== 404 &&\n !params?.for_aggs\n ) {\n throw new Error(GET_DATA_ERROR);\n } else if (response.data.report[0].status === 400) {\n throw new Error(\n 'Sorry! Something went wrong while fetching analytics logs.'\n );\n }\n return response;\n });\n },\n\n getElasticLogFilter: function (\n sentinel,\n jid,\n filter_key,\n filter_value,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_log_filter',\n nd: jid,\n ctx: { filter_key, filter_value },\n },\n sentinel,\n token\n ).then(response => {\n if (\n !!response.data.report[0] &&\n !!response.data.report[0].status &&\n response.data.report[0].status !== 200 &&\n response.data.report[0].status !== 404\n ) {\n throw new Error(GET_DATA_ERROR);\n } else if (response.data.report[0].status === 400) {\n throw new Error('Sorry! Something went wrong while fetching records.');\n } else if (\n !response.data.report.length ||\n response.data.report[0].status === 404\n ) {\n throw new Error('No records found');\n }\n return response;\n });\n },\n\n getElasticByIntervalLogDate: function (\n sentinel,\n jid,\n start_date,\n end_date,\n interval,\n integrationIds,\n categories,\n token,\n params\n ) {\n const snakedCaseParams = Object.keys(params).reduce((acc, key) => {\n if (typeof key === 'string') {\n return { ...acc, [snakeCase(key)]: params[key] };\n }\n return key;\n }, {});\n\n return this.jacPrimeRun(\n {\n name: 'get_graph',\n nd: jid,\n ctx: {\n start_date,\n end_date,\n start_range: start_date,\n end_range: end_date,\n interval,\n category_ids: categories || [],\n integration_ids: integrationIds || params?.interactionIds || [],\n ...snakedCaseParams,\n answer_ids: snakedCaseParams.answer_ids || [],\n },\n },\n sentinel,\n token\n ).then(response => {\n if (\n !!response.data.report[0] &&\n !!response.data.report[0].status &&\n response.data.report[0].status !== 200 &&\n response.data.report[0].status !== 404\n ) {\n throw new Error(GET_DATA_ERROR);\n } else if (response.data.report[0].status === 400) {\n throw new Error('Sorry! Something went wrong while fetching records.');\n } else if (response.data.report[0].status === 404) {\n throw new Error('No records found');\n } else if (\n (!response.data.report.length ||\n response.data.report[0].status === 404) &&\n !params?.for_aggs\n ) {\n throw new Error('No records found');\n }\n return response;\n });\n },\n\n getCallbackByDateRange: function (\n sentinel,\n jid,\n start_date,\n end_date,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_callback',\n nd: jid,\n ctx: { start_date, end_date },\n },\n sentinel,\n token\n ).then(response => {\n if (\n !!response.data.report[0] &&\n !!response.data.report[0].status &&\n response.data.report[0].status !== 200 &&\n response.data.report[0].status !== 404\n ) {\n throw new Error(GET_DATA_ERROR);\n } else if (response.data.report[0].status === 400) {\n throw new Error('Sorry! Something went wrong while fetching records.');\n } else if (\n !response.data.report ||\n !response.data.report.length ||\n response.data.report[0].status === 404\n ) {\n throw new Error('No records found');\n }\n return response;\n });\n },\n\n createCategory: function (sentinel, jid, name, color, token) {\n return this.jacPrimeRun(\n {\n name: 'create_category',\n nd: jid,\n ctx: { name, color },\n },\n sentinel,\n token\n );\n },\n\n getCategories: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_categories',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n updateCategory: function (sentinel, jid, name, color, token) {\n return this.jacPrimeRun(\n {\n name: 'change_category',\n nd: jid,\n ctx: { name, color },\n },\n sentinel,\n token\n );\n },\n\n deleteCategory: function (sentinel, jid, deleteAnswers, token) {\n const shouldDeleteAnswers = deleteAnswers ? 'yes' : 'no';\n return this.jacPrimeRun(\n {\n name: 'delete_category',\n nd: jid,\n ctx: { deleteanswers: shouldDeleteAnswers },\n },\n sentinel,\n token\n );\n },\n\n getCategoryTemplates: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_category_templates',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n importCategoryTemplates: function (sentinel, jid, token, categoryTemplates) {\n return this.jacPrimeRun(\n {\n name: 'import_category_template',\n nd: jid,\n ctx: { templates: categoryTemplates },\n },\n sentinel,\n token\n );\n },\n\n exportBot: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'exporter',\n ctx: {\n bot: jid,\n },\n },\n sentinel,\n token\n );\n },\n\n importBot: function (\n sentinel,\n graph,\n name,\n desc,\n metadata,\n answerbank,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'import_bot',\n nd: graph,\n ctx: { name, desc, metadata, answerbank },\n },\n sentinel,\n token\n );\n },\n\n importer: function (\n sentinel,\n graph,\n token,\n data,\n webSocketChannel,\n triggerID\n ) {\n return this.jacPrimeRun(\n {\n name: 'importer',\n nd: graph,\n ctx: { data, ws_target: webSocketChannel, trigger_id: triggerID },\n is_async: webSocketChannel ? true : false,\n },\n sentinel,\n token\n );\n },\n\n createIntegration: function (sentinel, jid, data, type, token) {\n return this.jacPrimeRun(\n {\n name: 'create_integration',\n nd: jid,\n ctx: {\n identifier: data.identifier,\n int_type: type,\n path: data.path,\n settingsobj: data.settings,\n },\n },\n sentinel,\n token\n );\n },\n\n setViberWebHook: function (sentinel, jid, payload, token) {\n return this.jacPrimeRun(\n {\n name: 'viber_set_webhook',\n nd: jid,\n ctx: payload,\n },\n sentinel,\n token\n );\n },\n\n getIntegration: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_integrations',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n editIntegration: function (sentinel, jid, data, type, token) {\n return this.jacPrimeRun(\n {\n name: 'change_integration',\n nd: jid,\n ctx: {\n identifier: data.identifier,\n int_type: type,\n path: data.path,\n settingsobj: data.settings,\n },\n },\n sentinel,\n token\n );\n },\n\n deleteIntegration: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_integration',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n createTestSuite: function (sentinel, jid, name, token) {\n return this.jacPrimeRun(\n {\n name: 'create_testsuite',\n nd: jid,\n ctx: { name },\n },\n sentinel,\n token\n );\n },\n\n getTestSuites: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_testsuites',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n updateTestSuite: function (sentinel, jid, name, token) {\n return this.jacPrimeRun(\n {\n name: 'change_testsuite',\n nd: jid,\n ctx: { name },\n },\n sentinel,\n token\n );\n },\n\n deleteTestSuite: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_testsuite',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n getTestCases: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_testcases',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n createTestCase: function (sentinel, jid, question, response, token) {\n return this.jacPrimeRun(\n {\n name: 'create_testcase',\n nd: jid,\n ctx: { question, answer: response.text },\n },\n sentinel,\n token\n );\n },\n\n updateTestCase: function (sentinel, jid, question, response, token) {\n return this.jacPrimeRun(\n {\n name: 'change_testcase',\n nd: jid,\n ctx: { question, answer: response.show_text },\n },\n sentinel,\n token\n );\n },\n\n deleteTestCase: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_testcase',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n runTestSuite: function (sentinel, jid, name, token, use_draft) {\n return this.jacPrimeRun(\n {\n name: 'run_testsuite',\n nd: jid,\n ctx: { name, use_draft },\n },\n sentinel,\n token\n );\n },\n\n runAllTests: function (sentinel, jid, token, use_draft) {\n return this.jacPrimeRun(\n {\n name: 'run_all_tests',\n nd: jid,\n ctx: { use_draft },\n },\n sentinel,\n token\n );\n },\n\n getQuestionLog: function (\n sentinel,\n jid,\n answerid,\n token,\n start_date,\n end_date\n ) {\n const endDateEndofDay = convertToEndOfDay(end_date);\n return this.jacPrimeRun(\n {\n name: 'get_question_log',\n nd: jid,\n ctx: {\n answer_ids: answerid,\n start_date,\n end_date: endDateEndofDay,\n integration_ids: [],\n },\n },\n sentinel,\n token\n );\n },\n\n export: function (sentinel, jid, token, params) {\n return this.jacPrimeRun(\n {\n name: 'exporter',\n ctx: params,\n },\n sentinel,\n token\n );\n },\n\n getDefaultAnswer: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_default_answer',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n setOnboardingFlag: function (sentinel, token, graph, flag) {\n return this.jacPrimeRun(\n {\n name: 'set_onboarding_flag',\n nd: graph,\n ctx: { flag },\n },\n sentinel,\n token\n );\n },\n\n resetOnboardingFlag: function (sentinel, token, graph, flag) {\n return this.jacPrimeRun(\n {\n name: 'reset_onboarding_flag',\n nd: graph,\n ctx: { flag },\n },\n sentinel,\n token\n );\n },\n\n resetAllOnboardingFlag: function (sentinel, token, graph) {\n return this.jacPrimeRun(\n {\n name: 'reset_onboarding',\n nd: graph,\n },\n sentinel,\n token\n );\n },\n\n createManyTestCases: function (sentinel, token, graph, questions, isDefault) {\n return this.jacPrimeRun(\n {\n name: 'create_many_testcases',\n nd: graph,\n ctx: { questions: [...questions], is_default: isDefault },\n },\n sentinel,\n token\n );\n },\n\n getBotSet: function (nd, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_botset',\n nd,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n changeBotSet: function (sentinel, token, graph, ctx) {\n return this.jacPrimeRun(\n {\n name: 'change_botset',\n nd: graph,\n ctx,\n },\n sentinel,\n token\n );\n },\n\n resetBotset: function (sentinel, token, graph) {\n return this.jacPrimeRun(\n {\n name: 'reset_botset',\n nd: graph,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n createJiraTicket: function (\n title,\n body,\n reporter,\n files,\n token,\n sentinel,\n graph\n ) {\n return this.jacPrimeRunMultipart(\n {\n name: 'jira_create_issue',\n ctx: {\n projectKey: '10000',\n title,\n body,\n labels: [`Reporter:${reporter.replaceAll(' ', '_')}`],\n issueType: '10001',\n ...files,\n },\n },\n sentinel,\n graph,\n token\n );\n },\n\n getAllJiraTicket: function (reporter, token, sentinel, startAt) {\n return this.jacPrimeRun(\n {\n name: 'jira_get_issue',\n ctx: {\n jql: `Labels=\"Reporter:${reporter.replaceAll(' ', '_')}\"`,\n startAt,\n maxResults: 5,\n },\n },\n sentinel,\n token\n );\n },\n\n getJiraIssue: function (ticket, graph, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'jira_get_issue',\n nd: graph,\n ctx: {\n issueIdOrKey: ticket,\n },\n },\n sentinel,\n token\n );\n },\n\n downloadJiraAttachment: function (id, graph, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'jira_download_attachment',\n nd: graph,\n ctx: {\n attachmentId: id,\n },\n },\n sentinel,\n token\n );\n },\n\n generateFBMessengerUUID: function (data) {\n return axios\n .post(`${WEBHOOK_MESSENGER}generate-uuid`, data)\n .catch(error => error.response);\n },\n\n setViberHook: function (data) {\n return axios.post(WEBHOOK_VIBER, data).catch(error => error.response);\n },\n\n globalVarGetter: function (name, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_global_var',\n ctx: {\n name,\n },\n },\n sentinel,\n token\n );\n },\n\n getActivityLogs: function (sentinel, token, params, size) {\n return this.jacPrimeRun(\n {\n name: 'get_activity_log',\n ctx: {\n ...params,\n from: 0,\n size,\n },\n snt: 'active:sentinel',\n nd: 'active:graph',\n },\n sentinel,\n token\n );\n },\n\n getActivityLogsFilters: function (sentinel, token, master_id, all, params) {\n return this.jacPrimeRun(\n {\n name: 'get_activity_log_filters',\n snt: 'active:sentinel',\n nd: 'active:graph',\n ctx: { master_id: all ? null : master_id, all, ...params },\n },\n sentinel,\n token\n );\n },\n\n sigInToGoogle: function (data) {\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: `/auth/google/`,\n method: 'post',\n data,\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n },\n\n getSimilarQuestions: function (text, sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_similar_questions',\n nd: jid,\n ctx: {\n text,\n },\n },\n sentinel,\n token\n );\n },\n\n hardLinkQuestion: function (\n question,\n answerId,\n jid,\n version,\n sentinel,\n token,\n withAnswer = false\n ) {\n return this.jacPrimeRun(\n {\n name: 'hard_link_question',\n nd: jid,\n ctx: {\n question,\n answer_id: answerId,\n report_with_answer: withAnswer,\n question_version: version,\n },\n },\n sentinel,\n token\n );\n },\n\n deleteQuestionHardLink: function (questionId, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_question',\n nd: questionId,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n deleteQuestionThenHardLinkAnswer: function (\n questionId,\n questionText,\n answerid,\n botJID,\n sentinel,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'delete_question_then_link_answer',\n nd: botJID,\n ctx: {\n question_id: questionId,\n question: questionText,\n answer_id: answerid,\n },\n },\n sentinel,\n token\n );\n },\n\n updateQuestionValidation: function (sentinel, jid, token, validationDetails) {\n return this.jacPrimeRun(\n {\n name: 'update_log_validation',\n nd: jid,\n ctx: { ...validationDetails },\n },\n sentinel,\n token\n );\n },\n\n getSimilarAnswersWithID: function (answer, sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_similar_answers_with_id',\n nd: jid,\n ctx: {\n answer_obj: answer,\n },\n },\n sentinel,\n token\n );\n },\n\n getQuestionMatchesWithAnswer: function (\n text,\n num_matches,\n sentinel,\n jid,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_question_matches_with_answer',\n nd: jid,\n ctx: {\n text,\n num_matches,\n },\n },\n sentinel,\n token\n );\n },\n\n getSimilarAnswer: function (sentinel, jid, text, answerId = null, token) {\n return this.jacPrimeRun(\n {\n name: 'get_similar_answers',\n nd: jid,\n ctx: {\n text: text,\n //revert this to answer_id once BE refactor is deployed\n answer_id: isZSBUUID(answerId) ? answerId : undefined,\n },\n },\n sentinel,\n token\n );\n },\n\n syncSentinelGlobal: function (master_id, token) {\n return this.jacSyncSentinelGlobal(\n {\n name: 'sentinel_active_global',\n ctx: {\n master_id,\n },\n },\n token\n );\n },\n\n getHardLinkQuestionsFromAnswer: function (answerId, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_questions_from_answer',\n nd: answerId,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n getHardLinkQuestionsFromAnswerWithScore: function (\n answerId,\n sentinel,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_questions_from_answer_with_score',\n nd: answerId,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n editQuestion: function (\n text,\n answerId,\n questionId,\n version,\n sentinel,\n token,\n withAnswer = false\n ) {\n return this.jacPrimeRun(\n {\n name: 'change_question',\n nd: questionId,\n ctx: {\n text,\n answer_id: answerId,\n report_with_answer: withAnswer,\n question_version: version,\n },\n },\n sentinel,\n token\n );\n },\n\n getAllQuestionsWithAnswer: function (jid, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_all_questions_with_answer',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n // use this to fetch question score\n // or question that is NOT LINKED\n // returns similar questions if found\n getAnswersWithScoreFromQuestion: function (\n text,\n jid,\n sentinel,\n token,\n withDefaultAnswers\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_answers_with_score_from_question',\n nd: jid,\n ctx: {\n question: text,\n with_default: withDefaultAnswers,\n },\n },\n sentinel,\n token\n );\n },\n\n // question is ALREADY LINKED to an answer\n // use this to fetch question score to all answers\n // and you want to link to new answer\n getAnswersWithScoreFromQuestionWithLinkedAnswer: function (\n question,\n answerId,\n jid,\n sentinel,\n token\n ) {\n return this.jacPrimeRun(\n {\n name: 'get_answers_with_score_from_question_with_linked_answer',\n nd: jid,\n ctx: {\n question,\n answer_id: answerId,\n },\n },\n sentinel,\n token\n );\n },\n getQuestion: function (questionId, sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_question',\n nd: questionId,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n addFeatureFlag: function (feature, currentVersion) {\n return this.jacPrimeRun({\n name: 'add_version',\n ctx: {\n ver: 'v1.0.1',\n features: {\n [feature]: true,\n },\n },\n });\n },\n\n getPlansWithDetails: function (sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'get_plans',\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n syncBot: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'sync_bot',\n nd: jid,\n },\n sentinel,\n token\n );\n },\n\n getFiles: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'get_files',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n addFile: function (\n { file, ver, pages, fileType, webSocketChannel, preConfigs },\n token,\n jid,\n sentinel\n ) {\n let formData;\n\n const ctxData = {\n pages,\n pre_configs: preConfigs,\n ver,\n ws_target: webSocketChannel || null,\n };\n\n if (fileType === 'url') {\n return this.jacPrimeRun(\n {\n name: 'add_file',\n nd: jid,\n ctx: ctxData,\n is_async: webSocketChannel ? true : false,\n },\n sentinel,\n token\n );\n } else {\n formData = new FormData();\n formData.append('nd', jid);\n formData.append('files', file);\n formData.append('ctx', JSON.stringify(ctxData));\n formData.append(\n 'is_async',\n JSON.stringify(webSocketChannel ? true : false)\n );\n axios.defaults.headers.common['Authorization'] = 'token ' + token;\n return axios\n .request({\n baseURL: API_BASE_URL,\n url: '/js/walker_run?name=add_file',\n method: 'post',\n data: formData,\n headers: {\n 'Content-Type': 'multipart/form-data',\n },\n })\n .catch(error => {\n throw new Error(getErrorMessage(error));\n });\n }\n },\n\n scrappedUrl: function (\n {\n url,\n uploadingMethod,\n timeoutInSecond,\n crawler,\n webSocketChannel,\n method,\n expression,\n triggerID,\n },\n token,\n jid,\n sentinel\n ) {\n const getters = {\n method: method ? method : 'default',\n expression,\n };\n\n if (!getters.expression) {\n delete getters.expression;\n }\n\n return this.jacPrimeRun(\n {\n name: 'scrape',\n nd: jid,\n ctx: {\n pages: [\n {\n goto: {\n url,\n wait_until: uploadingMethod,\n timeout: timeoutInSecond * 1000,\n },\n getters: [getters],\n crawler,\n },\n ],\n pre_configs: renderPreconfigData(uploadingMethod, timeoutInSecond, [\n {\n method: 'none',\n },\n ]),\n ws_target: webSocketChannel || null,\n is_async: webSocketChannel ? true : false,\n trigger_id: triggerID,\n },\n },\n sentinel,\n token\n );\n },\n\n deleteFile: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'delete_file',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n downloadFile: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'download_file',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n generateFileCdnUrl: function (sentinel, jid, token) {\n return this.jacPrimeRun(\n {\n name: 'cdn_url_file',\n nd: jid,\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n changeFile: function (sentinel, jid, token, fileDetails) {\n const { version, categoryId } = fileDetails;\n return this.jacPrimeRun(\n {\n name: 'change_file',\n nd: jid,\n ctx: {\n ver: version,\n category: typeof categoryId !== 'undefined' ? categoryId : undefined,\n },\n },\n sentinel,\n token\n );\n },\n\n changeWebsite: function (sentinel, jid, token, websiteDetails) {\n return this.jacPrimeRun(\n {\n name: 'change_file',\n nd: jid,\n ctx: websiteDetails,\n },\n sentinel,\n token\n );\n },\n\n scrapePreview: function (page, token, jid, sentinel) {\n return this.jacPrimeRun(\n {\n name: 'scrape_preview',\n nd: jid,\n ctx: {\n page,\n },\n },\n sentinel,\n token\n );\n },\n\n initializeApiGateway: function (sentinel, token, key, wlk) {\n return this.jacPrimeRun(\n {\n name: 'initialize_api_gateway',\n ctx: {\n wlk,\n key,\n },\n },\n sentinel,\n token\n );\n },\n\n removeAPIGateway: function (sentinel, token) {\n return this.jacPrimeRun(\n {\n name: 'remove_api_gateway',\n ctx: {},\n },\n sentinel,\n token\n );\n },\n\n stopScrapping: function (triggerID, token, jid, sentinel) {\n return this.jacPrimeRun(\n {\n name: 'scrape_stop ',\n nd: jid,\n ctx: {\n trigger_id: triggerID,\n },\n },\n sentinel,\n token\n );\n },\n};\n","export const ANSWER_TYPES = ['answer', 'file', 'website'];\nexport const ANSWER_FILE_TYPES = ['file', 'website'];\nexport const WEBSITE_NO_PAGE_SELECTED_ERROR_MESSAGE = 'Select atleast 1 page!';\n","import { cssVariables } from 'styles/root';\nimport styled from 'styled-components';\nimport Button from 'components/Button';\n\nexport const StyledWebsiteEditor = styled.div`\n display: flex;\n flex-direction: column;\n min-width: 100%;\n min-height: 20vh;\n margin-top: 1%;\n\n @media (max-width: 600px) {\n min-width: 80%;\n }\n\n .ant-descriptions-title {\n font-weight: normal;\n }\n`;\n\nexport const ActionContainer = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin: 16px 0;\n color: ${props => (props.error ? `red` : `rgba(0, 0, 0, 0.85)`)};\n`;\n\nexport const SpaceBetweenWrapper = styled.div`\n display: flex;\n justify-content: space-between;\n margin-top: ${props => (props.isTitle ? '2%' : '0')};\n margin-bottom: 3px;\n`;\n\nexport const StyledBtnAdvanceSettings = styled.button`\n display: flex;\n border: none;\n height: 38px;\n background: #ffff;\n color: #1667e7;\n border-radius: 1px;\n cursor: pointer;\n margin-top: 15px;\n float: right;\n line-height: 32px;\n\n &:hover {\n background: #1667e726;\n }\n`;\n\nexport const StyledCodeButton = styled(Button)`\n background-color: ${cssVariables.primaryBlue} !important;\n text-transform: capitalize;\n max-width: 200px;\n color: #fff !important;\n\n &.secondary {\n color: ${cssVariables.primaryBlue} !important;\n background-color: transparent !important;\n }\n\n & span {\n font-weight: ${cssVariables.font.normal};\n letter-spacing: 0px;\n }\n`;\n","const ROUTES = {\n HOME: '/',\n LOGIN: '/login',\n EXTERNAL_LOGIN: '/external-login',\n SIGNIN: '/sign-in',\n SIGNUP: '/signup',\n EXTERNAL_SIGNUP: '/external-signup',\n LOGOUT: '/logout',\n BOTS_PAGE: '/bots',\n ADMIN_PAGE: '/admin',\n ADMIN_USERS: '/admin/users',\n ADMIN_GLOBAL_VARS: '/admin/global-vars',\n ADMIN_ACTIVITY_LOGS: '/admin/activity-logs',\n ADMIN_VERSIONS: '/admin/versions',\n BOT_DETAILS: '/bot',\n USER_PROFILE: '/profile',\n USER_PASSWORD: '/profile/password',\n PLAN_AND_PAYMENTS: '/profile/plan',\n LANGUAGE_AND_REGION: '/profile/language',\n SECURITY: '/profile/security',\n INTEGRATION: '/profile/integration',\n ACTIVITY_LOGS: '/profile/activity-logs',\n RESET_PASSWORD: '/reset-password',\n CONFIRM_NEW_PASSWORD: '/confirm-new-password',\n SUBSCRIPTION: '/profile/subscription',\n ONBOARDING_FLAGS: '/profile/onboarding-flags',\n VERIFY_EMAIL: '/activation',\n VALIDATION: '/automation-testing',\n BOT_INTEGRATION: '/integration',\n CALLBACK_LOGS: '/callback-logs',\n FUNCTIONS: '/functions',\n BOT_SETTINGS: '/bot-settings',\n QUESTIONS: '/linked-questions',\n ANSWERBANK: '/answer-bank',\n ANSWERLIST: '/answer-bank/answers',\n ANALYTICS: '/analytics',\n BOT_OVERVIEW: '/overview',\n ANALYTICS_QUESTION_LOGS: '/analytics#question-log',\n MOST_ASKED_QUESTIONS: '/analytics#most-asked-question',\n ANALYTICS_CATEGORY: '/analytics#most-asked-question?category=',\n FLOW_SIMULATOR: '/flow-simulator',\n API_KEY: '/profile/api-key',\n};\n\nexport default ROUTES;\n","import { ZSB_PRICING_PAGE } from 'constants/outboundLinks';\nimport { strippedString } from './stringManipulation';\n\nconst multiSplit = (str, delimiters, firstColSplit) => {\n return delimiters.reduce((acc, cur) => {\n if (typeof acc === 'string') {\n return acc.split(cur);\n }\n if (firstColSplit) {\n return acc.map(a => a.split(cur)[0]).flat(1);\n } else {\n return acc.map(a => a.split(cur)).flat(1);\n }\n }, str);\n};\n\nexport const generateRandomColor = () =>\n '#' + Math.floor(Math.random() * 16777215).toString(16);\n\nexport const getBase64 = file => {\n if (file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.readAsDataURL(file);\n reader.onload = () => resolve(reader.result);\n reader.onerror = error => reject(error);\n });\n }\n return null;\n};\n\nexport const csvToArray = (str, delimiter, readOnlyFirstCol) => {\n const csvDelimiters = [',', '\\n'];\n const csvData = multiSplit(str, delimiter || csvDelimiters, readOnlyFirstCol);\n\n return csvData.filter(\n item => !!item && !item.match(new RegExp(/[^\\x00-\\x7F]/g)) && item.trim()\n );\n};\n\nexport const stripUUID = uuid => {\n return typeof uuid === 'string' ? uuid.split(':')?.pop() : '';\n};\n\nexport const withPrefixUUID = uuid => {\n // uuid either coming from gloabl state\n // or url pathname\n const uuidPrefix = 'urn:uuid:';\n if (uuid.includes('/bot/')) {\n const strippedBotIDFromPath = uuid.split('/bot/').pop();\n if (strippedBotIDFromPath.includes('/')) {\n return `${uuidPrefix}${stripUUID(strippedBotIDFromPath.split('/')[0])}`;\n }\n return `${uuidPrefix}${stripUUID(strippedBotIDFromPath)}`;\n }\n const strippedBotID = uuid.split('/')[0];\n return `${uuidPrefix}${stripUUID(strippedBotID)}`;\n};\n\nexport const formatBytes = bytes => {\n const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n let l = 0,\n n = parseInt(bytes, 10) || 0;\n\n while (n >= 1024 && ++l) {\n n = n / 1024;\n }\n return n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l];\n};\n\nexport const base64toBlob = (base64Data, contentType) => {\n contentType = contentType || '';\n var sliceSize = 1024;\n var byteCharacters = atob(base64Data);\n var bytesLength = byteCharacters.length;\n var slicesCount = Math.ceil(bytesLength / sliceSize);\n var byteArrays = new Array(slicesCount);\n\n for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {\n var begin = sliceIndex * sliceSize;\n var end = Math.min(begin + sliceSize, bytesLength);\n\n var bytes = new Array(end - begin);\n for (var offset = begin, i = 0; offset < end; ++i, ++offset) {\n bytes[i] = byteCharacters[offset].charCodeAt(0);\n }\n byteArrays[sliceIndex] = new Uint8Array(bytes);\n }\n return new Blob(byteArrays, { type: contentType });\n};\n\nexport const escapeCSV = str => {\n if (str.match && str.match(/,|\"|\\n|\\r|\\n\\n/)) {\n return `\"${str.replaceAll(/\"/g, '\"\"')}\"`;\n } else {\n return str;\n }\n};\n\nexport const getCurrentBrowser = () => {\n const { userAgent } = navigator;\n let match =\n userAgent.match(\n /(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i\n ) || [];\n let temp;\n\n if (/trident/i.test(match[1])) {\n temp = /\\brv[ :]+(\\d+)/g.exec(userAgent) || [];\n\n return `IE ${temp[1] || ''}`;\n }\n\n if (match[1] === 'Chrome') {\n temp = userAgent.match(/\\b(OPR|Edge)\\/(\\d+)/);\n\n if (temp !== null) {\n return temp.slice(1).join(' ').replace('OPR', 'Opera');\n }\n\n temp = userAgent.match(/\\b(Edg)\\/(\\d+)/);\n\n if (temp !== null) {\n return temp.slice(1).join(' ').replace('Edg', 'Edge (Chromium)');\n }\n\n if (navigator.brave) {\n return 'Brave (Chromium)';\n }\n }\n\n match = match[2]\n ? [match[1], match[2]]\n : [navigator.appName, navigator.appVersion, '-?'];\n temp = userAgent.match(/version\\/(\\d+)/i);\n\n if (temp !== null) {\n match.splice(1, 1, temp[1]);\n }\n\n return match.join(' ');\n};\n\nexport const isM1Chip = () => {\n return !!navigator.userAgent.match(/OS X 10_([789]|1[0123456789])/);\n};\n\n// This is handy to filter out specific keys from the object\n// Data types: {obj} -> Array of Objects, {keys} -> array of strings !!\nexport const filterObjectKeys = (obj, keys) => {\n // reject object of objects\n if (obj[0]) {\n return false;\n }\n const keysToExclude = Array.isArray(keys)\n ? keys\n : typeof keys === 'string'\n ? [keys]\n : [];\n\n const filteredObject = Object.entries(obj).filter(([key, value]) => {\n // exclude if the DATA TYPE of {key} is not a String\n if (typeof key !== 'string') {\n return false;\n } else if (!keysToExclude.includes(key)) {\n return true;\n }\n return false;\n });\n\n // Convert the key/value array back to an object\n return Object.fromEntries(filteredObject);\n};\n\nexport const testEmailRegEx = email => {\n // HTML5 email regEx pattern\n const reg =\n /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n return reg.test(email);\n};\n\nexport const isCollectionValueHasDuplicate = (\n collection,\n valueToCompareForDuplicates,\n keyToCompareForDuplicates,\n id\n) => {\n const strippedInput = strippedString(valueToCompareForDuplicates);\n return !strippedInput\n ? false\n : collection.some(item => {\n return strippedString(item[keyToCompareForDuplicates]) ===\n strippedInput &&\n (item.jid\n ? stripUUID(item.jid) !== stripUUID(id)\n : stripUUID(item.id) !== stripUUID(id))\n ? true\n : undefined;\n });\n};\n\nexport const showLessTextCharacters = (\n text,\n showFullText,\n maxTextLength = 190\n) => {\n if (text) {\n if (text.length > maxTextLength && !showFullText) {\n return text.substring(0, maxTextLength) + '... ';\n }\n return text;\n }\n return null;\n};\n\nexport const makeKeywordAsAnchorText = toolTipTitle => {\n const keywords = ['Upgrade', 'Contact Us'];\n let resultString = toolTipTitle;\n\n keywords.forEach(substring => {\n const regex = new RegExp(substring, 'gi');\n if (typeof resultString === 'string' && resultString.match(regex)) {\n resultString = resultString.replace(\n regex,\n `${substring} `\n );\n }\n });\n\n return ;\n};\n\n// use to get data from api response that's multidimensional\n// e.g. report[0][0]\nexport const getNodesFromArray = (data = []) => {\n if (Array.isArray(data) && data.length) {\n const isTheData = data.some(i => i?.kind === 'node');\n // if not the list with the matching node\n // get the first element of the array\n // then loop\n return isTheData ? data : getNodesFromArray(data.shift());\n }\n return data;\n};\n\nexport const handlePageResizing = () => {\n const windowWidth = window.innerWidth;\n if (windowWidth < 377) {\n return {\n isMobileView: true,\n standardFontSize: '.5rem',\n };\n } else if (windowWidth < 639) {\n return {\n isMobileView: true,\n standardFontSize: '.7rem',\n };\n } else if (windowWidth < 769) {\n return {\n isMobileView: false,\n standardFontSize: '.9rem',\n };\n } else {\n return {\n isMobileView: false,\n standardFontSize: '1rem',\n };\n }\n};\n","export const DEFAULT_ERROR_MESSAGE =\n 'Something went wrong while doing that. Please contact Admin support or try again later.';\n\nexport const GET_DATA_ERROR = 'Something went wrong while fetching your data';\n\nexport const PLAN_LIMIT_ERROR =\n 'You have reached your plan limit. Please upgrade your plan.';\n","export const DEFAULT_EDITOR = 'rte';\nexport const WEBSITE_SLUG = 'websites';\nexport const FILE_SLUG = 'files';\nexport const ANSWER_SLUG = 'answers';\nexport const EDITORS = {\n RICH_TEXT: 'rte',\n HTML: 'html',\n};\n\nexport const EDITOR_OPTIONS = [\n { label: 'Rich Text Editor', value: 'rte' },\n { label: 'HTML Editor', value: 'html' },\n];\n\nexport const ANSWER_TIP =\n 'Response entered here will be used to calculate the response score.';\nexport const DISPLAY_ANSWER_TIP =\n 'The response here will be displayed to the user. You can design your response here. Please note, ZSB will not calculate response score based on the response here.';\n\nexport const DEFAULT_ANSWER_VERSION = 'final';\nexport const DEFAULT_LANGUAGE = 'ENGLISH (EN)';\nexport const MAX_ANSWER_SCORE_TO_DISABLE_FINAL_VERSION = 0.95;\n\nexport const EMPTY_ANSWER_OBJECT = {\n jid: null,\n text: null,\n show_text: null,\n editor: DEFAULT_EDITOR,\n type: 'answer',\n score: '0.000',\n version: null,\n answerLength: 0,\n};\n\nexport const CREATE_ANSWER_MANUAL_SOURCE = 'manual';\nexport const CREATE_ANSWER_MANUAL_SOURCE_TYPE = 'manual';\nexport const CATEGORY_FILTER_TOOLTIP = 'View questions asked in this category';\n\nexport const ZSB_CHAT_BREAKER_ENCONDING = '<zsb-chat-breaker />';\nexport const ZSB_CHAT_BREAKER_ELEMENT = ' ';\n\nexport const SCRAPPER_DEFAULT_POST_SCRIPTS = [\n {\n method: 'evaluate',\n expression:\n 'try { document.querySelector(\"textarea[id=APjFqb]\").value = \"speed test\"; document.querySelector(\"form[action=\\'/search\\']:has(input[type=submit][value=\\'Google Search\\'])\").submit(); } catch (err) { }',\n },\n {\n method: 'wait_for_selector',\n selector: '#result-stats',\n state: 'visible',\n },\n];\n\nexport const SEARCH_KEY_INITIAL_VALUE = {\n answer: '',\n file: '',\n website: '',\n};\n","import { isBoolean, isString, isEqual } from 'lodash';\n\nexport const isNumber = x => {\n if (!x) {\n return false;\n }\n if (typeof x === 'number') {\n return true;\n }\n if (typeof x !== 'string') {\n return false;\n }\n\n const canParse = !isNaN(Number.parseInt(x));\n return canParse && `${Number.parseInt(x)}` === x;\n};\n\nexport const isAnObject = value => {\n if (!value) {\n return false;\n }\n\n return typeof value === 'object' &&\n value instanceof Object &&\n !Array.isArray(value)\n ? true\n : false;\n};\n\nexport const parseBoolean = input => {\n if (isBoolean(input)) {\n return input;\n }\n\n if (isString(input)) {\n const lowercasedInput = input?.toLowerCase();\n\n if (isEqual(lowercasedInput, 'true')) {\n return true;\n } else if (isEqual(lowercasedInput, 'false')) {\n return false;\n }\n }\n\n return false;\n};\n\nexport const isZSBUUID = uuidStr => {\n const zsbUUIDPattern =\n /[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/;\n return zsbUUIDPattern.test(uuidStr);\n};\n\nexport const getFileExtension = fileName => {\n const lastDotIndex = fileName.lastIndexOf('.');\n if (lastDotIndex === -1) {\n return '';\n }\n\n return fileName.slice(lastDotIndex + 1).toLowerCase();\n};\n\nexport const isImageOrDocFileType = fileName => {\n const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'];\n const ext = getFileExtension(fileName);\n\n return imageExtensions.includes(ext.toLowerCase())\n ? 'image'\n : ext\n ? 'doc'\n : 'invalid';\n};\n","export function appendWithParenthesesIfDuplicate(\n duplicatesToSearch,\n searchString\n) {\n const duplicateNameFound = duplicatesToSearch.find(\n str => str === searchString\n );\n\n let highestCount = 0;\n if (duplicateNameFound) {\n duplicatesToSearch.forEach(str => {\n const match = str.match(new RegExp(`${searchString} \\\\((\\\\d+)\\\\)$`));\n if (match) {\n const count = parseInt(match[1], 10);\n if (!isNaN(count) && count > highestCount) {\n highestCount = count;\n }\n }\n });\n\n return `${searchString} (${highestCount === 0 ? 1 : highestCount + 1})`;\n }\n\n return searchString;\n}\n\nexport const generateRandomString = () => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n let result = '';\n const charactersLength = chars.length;\n for (let i = 0; i < 12; i++) {\n result += chars.charAt(Math.floor(Math.random() * charactersLength));\n }\n return result;\n};\n\nexport const slugify = str => {\n str = str\n .replace(/[`~!@#$%^&*()_\\-+=\\[\\]{};:'\"\\\\|\\/,.<>?\\s]/g, ' ')\n .toLowerCase();\n str = str.replace(/^\\s+|\\s+$/gm, '');\n str = str.replace(/\\s+/g, '-');\n return str;\n};\n\nexport const strippedString = str => {\n if (!str) {\n return '';\n }\n if (Array.isArray(str)) {\n return str.map(i => i?.replace(/[^a-zA-Z0-9]/g, '').toLowerCase());\n } else if (!str || !str?.trim() || !str?.trim()?.length) {\n return '';\n }\n\n return str.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();\n};\n\nexport const stringLocaleCompare = (val1, val2) => {\n return !!val2 && !!val1 ? val1.localeCompare(val2) : !val2 && !!val1 ? -1 : 1;\n};\n\nexport const stripSpecialCharactersFromText = text => {\n if (!text) {\n return;\n }\n\n const symbolsRegex = /[\\|&;:\\$%@!\"<>\\(\\)\\+,]/g;\n return text.trim().toLowerCase().replace(symbolsRegex, '');\n};\n","import { orderBy } from 'lodash';\n\nimport {\n DEFAULT_EDITOR,\n DEFAULT_ANSWER_VERSION,\n EMPTY_ANSWER_OBJECT,\n ZSB_CHAT_BREAKER_ENCONDING,\n} from 'constants/answerbank/defaults';\nimport { DEFAULT_ANSWER_THRESHOLD } from 'constants/bot';\nimport { stripUUID } from 'utils';\nimport { extractQuickReplyData } from 'utils/answers';\nimport { isAnObject, parseBoolean } from 'utils/dataTypes';\nimport {\n ANSWER_FILE_TYPES,\n WEBSITE_NO_PAGE_SELECTED_ERROR_MESSAGE,\n} from 'constants/answerbank/answertype';\nimport Button from 'components/Button';\nimport { ActionContainer } from 'components/Modals/WebsiteEditor/WebsiteEditor.styles';\nimport { Tag, Tooltip, message } from 'antd';\nimport { ExclamationCircleOutlined } from '@ant-design/icons';\nimport confirm from 'antd/lib/modal/confirm';\nimport { SET_WEBSITE_SELECTED_PAGES } from 'store/action';\n\nconst extractOpenAIAnswerFromAPI = answerData => {\n const openaiObject =\n isAnObject(answerData) && answerData.openai\n ? answerData.openai\n : answerData;\n\n if (\n !isAnObject(openaiObject?.context) ||\n !isAnObject(openaiObject?.context?.openai)\n ) {\n return undefined;\n }\n const { openai } = openaiObject?.context;\n const {\n detected_language,\n intents,\n references,\n sentiments,\n target_language,\n translation,\n } = openai;\n\n return {\n detectedLanguage: detected_language,\n intents,\n references,\n sentiments,\n targetLanguage: target_language,\n translation,\n };\n};\n\nconst renameAnswerAPIvariables = (answerData, isBotOpenAIEnabled) => {\n // new importer returns\n /**\n * answerData: {\n * answer: {\n * ...answerObjectResponseFromAPI\n * }\n * }\n */\n const answerObject =\n // check whether answerData is from new importer\n isAnObject(answerData) && answerData.answer\n ? answerData.answer\n : answerData;\n const currentISODate = new Date().toISOString();\n const answerObjectScore = answerObject?.score;\n\n const {\n text,\n editor,\n qlinks,\n categoryid,\n last_updated_time,\n show_text,\n show_html,\n hitcount,\n score: contextScore,\n callback,\n ans_version,\n question_id,\n created_time,\n category_id,\n ver,\n category,\n sequence,\n scraping_info,\n } = answerObject.context;\n const isNotAnAnswer = ANSWER_FILE_TYPES.includes(answerObject.name);\n const version = isNotAnAnswer ? ver : ans_version || DEFAULT_ANSWER_VERSION;\n const { quick_reply, quick_reply_options, messageInputIsDisabled } =\n extractQuickReplyData(answerObject);\n\n const score = !isNaN(answerObjectScore)\n ? Number(answerObjectScore).toFixed(3)\n : !isNaN(contextScore)\n ? Number(contextScore).toFixed(3)\n : '0.000';\n\n return {\n jid: answerObject.jid,\n text: isNotAnAnswer ? answerObject.context?.name : text,\n qlinks: Array.isArray(qlinks) ? qlinks : [],\n categoryId:\n category && isAnObject(category)\n ? stripUUID(category?.jid)\n : typeof category === 'string'\n ? category\n : category_id || categoryid,\n lastEdited: last_updated_time || currentISODate,\n created_time: created_time,\n show_text: show_text,\n show_html: show_html || show_text,\n editor: editor || DEFAULT_EDITOR,\n hitcount: hitcount,\n type: answerObject.name,\n score: score,\n quickReply: quick_reply,\n requestAgent: parseBoolean(callback),\n quickReplyOptions: quick_reply_options || null,\n // can be use to enable / disable chat input\n // especially if quick reply is linked to an answer\n messageInputIsDisabled,\n version,\n questionId: question_id,\n answerLength: text?.length,\n openai: isBotOpenAIEnabled\n ? extractOpenAIAnswerFromAPI(answerData)\n : undefined,\n sequence: sequence || [],\n scrapingInfo: scraping_info || null,\n };\n};\n\nexport const extractGeneratedAnswer = (text, jid) => {\n return {\n jid,\n text,\n show_text: text,\n show_html: text,\n score: 0,\n hitcount: 0,\n htmlString: text,\n type: 'answer',\n };\n};\n\nexport const extractDefaultAnswer = (context, jid) => {\n return {\n jid,\n text: context.text,\n show_text: context.show_text,\n show_html: context.show_html || context.show_text,\n score: Number(context?.thresh_score) || DEFAULT_ANSWER_THRESHOLD,\n hitcount: context.hitcount,\n htmlString: Array.isArray(context.show_html)\n ? context.show_html.join(ZSB_CHAT_BREAKER_ENCONDING)\n : context.show_html\n ? String(context.show_html)\n : null,\n type: 'default_answer',\n };\n};\n\nconst renameFileAPIvariables = fileData => {\n const currentISODate = new Date().toISOString();\n const {\n name,\n last_updated_time,\n created_time,\n ver,\n hitcount,\n scraping_info,\n } = fileData.context;\n return {\n jid: fileData.jid,\n text: name,\n qlinks: [],\n lastEdited: last_updated_time || currentISODate,\n created_time: created_time,\n show_text: [name],\n show_html: [name],\n editor: DEFAULT_EDITOR,\n type: scraping_info ? 'website' : fileData.name,\n version: ver || DEFAULT_ANSWER_VERSION,\n hitcount: hitcount,\n scrapingInfo: scraping_info,\n };\n};\n\nexport const extractCategoryData = categoryFromAPI => {\n const { jid, context } = categoryFromAPI;\n return {\n jid: stripUUID(jid),\n name: context.name,\n color: context.color,\n };\n};\n\n/**\n * answerData = data from API\n * isBotOpenAIEnabled = value fetched from state.bot.useOpenAI\n */\nexport const extractAnswerData = (answerData, isBotOpenAIEnabled) => {\n if (Array.isArray(answerData) && answerData.length) {\n return answerData.map(item => {\n if (item.answer && item.answer?.name.includes('default')) {\n const { jid, context } = item.answer;\n return extractDefaultAnswer(context, jid);\n } else if (!item.answer && item.name.includes('default')) {\n const { jid, context } = item;\n return extractDefaultAnswer(context, jid);\n }\n // api returns an empty answer object\n else if (!Object.keys(item).length && !Object.keys(item?.answer).length) {\n return EMPTY_ANSWER_OBJECT;\n }\n return renameAnswerAPIvariables(item, isBotOpenAIEnabled);\n });\n } else if (Array.isArray(answerData)) {\n return [];\n }\n // api returns an empty answer object\n else if (isAnObject(answerData)) {\n if (!Object.keys(answerData).length) {\n return EMPTY_ANSWER_OBJECT;\n }\n return answerData?.name?.includes('default')\n ? extractDefaultAnswer(answerData.context, answerData.jid)\n : renameAnswerAPIvariables(answerData, isBotOpenAIEnabled);\n }\n};\n\nexport const extractFileData = fileData => {\n if (Array.isArray(fileData) && fileData.length) {\n return fileData.map(item => {\n if (!Object.keys(item).length && !Object.keys(item).length) {\n return EMPTY_ANSWER_OBJECT;\n }\n return renameFileAPIvariables(item);\n });\n } else if (Array.isArray(fileData)) {\n return [];\n }\n // api returns an empty answer object\n else if (isAnObject(fileData)) {\n if (!Object.keys(fileData).length) {\n return EMPTY_ANSWER_OBJECT;\n }\n return renameFileAPIvariables(fileData);\n }\n};\n\nexport const pushCurrentAnswerAsTopAnswer = (answers, currentAnswerId) => {\n const filteredAnswers = orderBy(\n answers\n .filter(item => item && item.jid !== currentAnswerId)\n .filter(item => !item.type?.includes('default_answer'))\n .filter(Boolean),\n 'score',\n 'desc'\n );\n\n const currentAnswer = currentAnswerId\n ? answers.find(item => item.jid === currentAnswerId)\n : null;\n // postion hard link response to `answers[0]`\n // to be fetch as the `currentAnswer` on ResponsePickerModal\n return [currentAnswer, ...filteredAnswers].filter(Boolean);\n};\n\nexport const renderPreconfigData = (\n uploadingMethod,\n timeoutInSecond,\n getters\n) => {\n return [\n {\n regex: '.+',\n scraper: {\n goto: {\n wait_until: uploadingMethod,\n timeout: timeoutInSecond * 1000,\n },\n getters,\n },\n },\n ];\n};\n\nexport const extractScannedObject = scanned => {\n const traverse = Object.keys(scanned)?.reduce((acc, key) => {\n const value = scanned[key];\n\n if (value.hasOwnProperty('source')) {\n acc.push(value.source);\n } else {\n acc.push(key);\n }\n\n return acc;\n }, []);\n\n return [...new Set(traverse)];\n};\n\nexport const categorizedWebsitePages = urlList => {\n const isObject = urlList?.every(\n item => typeof item === 'object' && item !== null\n );\n\n return urlList?.reduce((acc, url) => {\n const { pathname } = new URL(isObject ? url.url : url);\n const pageName = pathname?.split('/').filter(Boolean)[0];\n let category = acc.find(cat => cat.name === pageName);\n\n if (!category) {\n category = {\n key: acc.length + 1 || 0,\n name: pageName ? pageName : isObject ? url.url : url,\n urlList: [],\n };\n acc.push(category);\n }\n\n category.urlList.push(url);\n return acc;\n }, []);\n};\n\nexport const renderWebsiteCategoryTableColumn = (\n handleOpenPages,\n urlSelectedCount\n) => [\n {\n title: Category ,\n dataIndex: 'name',\n key: 'name',\n render: (value, urlDetails) => (\n \n \n {value?.charAt(0).toUpperCase() + value?.slice(1) || 'General'}\n \n \n handleOpenPages(urlDetails)}\n value={\n <>\n {urlSelectedCount(urlDetails)}/{urlDetails.urlList.length}{' '}\n Selected Page\n {urlSelectedCount(urlDetails) > 1 ? 's' : ''}\n >\n }\n variant=\"link\"\n style={{ padding: 0 }}\n />\n \n ),\n width: '80%',\n },\n];\n\nexport const renderWebsitePagesTableColumn = [\n {\n title: URL ,\n dataIndex: 'url',\n key: 'url',\n render: (value, urlDetails) => (\n \n {value}\n {urlDetails?.error && (\n \n \n Error found.\n \n \n )}\n \n ),\n width: '80%',\n },\n // selectedTemplate === 'traversing'\n // ? {\n // title: 'URL Filter Condition',\n // dataIndex: 'action',\n // key: 'action',\n // render: (value, urlSource) => (\n // handleChangeFilterMethod(evt, urlSource.url)}\n // defaultValue={urlSource.filterMethod}\n // size=\"small\"\n // width=\"100%\"\n // >\n // {FILTER_METHOD_OPTIONS?.map((filterMethod, idx) => (\n // \n // {filterMethod.label}\n // \n // ))}\n // \n // ),\n // width: '20%',\n // }\n // : null,\n];\n\nexport const extractCategoryCountSelected = (\n category,\n urlSelected,\n categoryTableSource\n) => {\n return categoryTableSource\n ?.find(categoryList => categoryList.name === category)\n ?.urlList?.map(urlObj => urlObj.url)\n ?.filter(url => urlSelected?.includes(url)).length;\n};\n\nexport const websiteSelectionChangesConfirmationMessage = (\n record,\n selected,\n selectionState,\n dispatch\n) => {\n if (selectionState?.urlSelected?.length === 1 && !selected) {\n return message.error(WEBSITE_NO_PAGE_SELECTED_ERROR_MESSAGE);\n } else {\n return confirm({\n title: `Do you really want to ${selected ? 'select' : 'unselect'} this?`,\n content: `This will ${\n selected ? 'select' : 'unselect'\n } all of the pages inside ${\n record.name?.charAt(0).toUpperCase() + record.name?.slice(1)\n }.`,\n okText: 'Proceed',\n cancelText: 'Cancel',\n onOk() {\n let payload = [];\n const selectedUrlList = record?.urlList?.map(urlObj => urlObj.url);\n const selectedKeyList = record?.urlList?.map(urlObj => urlObj.key);\n const unSelectedUrl = selectionState.urlSelected?.filter(\n url => !selectedUrlList?.includes(url)\n );\n const unSelectedKey = selectionState.urlSelectedRowKeys?.filter(\n key => !selectedKeyList?.includes(key)\n );\n\n if (selected) {\n payload = {\n urlSelectedRowKeys: [...unSelectedKey, ...selectedKeyList],\n urlSelected: [...unSelectedUrl, ...selectedUrlList],\n categorySelectedRowKeys: [\n ...selectionState.categorySelectedRowKeys,\n record.key,\n ],\n categorySelected: [...selectionState.categorySelected, record.name],\n };\n } else {\n payload = {\n categorySelectedRowKeys:\n selectionState?.categorySelectedRowKeys?.filter(\n state => state !== record.key\n ),\n categorySelected: selectionState?.categorySelected?.filter(\n state => state !== record.name\n ),\n urlSelected: unSelectedUrl,\n urlSelectedRowKeys: unSelectedKey,\n };\n }\n\n dispatch({\n type: SET_WEBSITE_SELECTED_PAGES,\n payload,\n });\n },\n });\n }\n};\n\nexport const handleChangeRowSelectionWebsiteCategoryTable = (\n selectionState,\n dispatch\n) => {\n return {\n selectedRowKeys: selectionState.categorySelectedRowKeys,\n onSelect: (record, selected) =>\n websiteSelectionChangesConfirmationMessage(\n record,\n selected,\n selectionState,\n dispatch\n ),\n };\n};\n\nexport const handleChangeRowSelectionWebsitePagesTable = (\n categoryTableSource,\n selectionState,\n dispatch\n) => {\n return {\n selectedRowKeys: selectionState.urlSelectedRowKeys,\n onSelect: (record, selected) => {\n if (selectionState?.urlSelected?.length === 1 && !selected) {\n return message.error(WEBSITE_NO_PAGE_SELECTED_ERROR_MESSAGE);\n } else {\n const selectedUrlCategory = categoryTableSource?.find(item =>\n item.urlList?.map(urlObj => urlObj.url)?.includes(record.url)\n );\n const unSelectedUrlCategory = selectionState?.categorySelected?.filter(\n category => category !== selectedUrlCategory?.name\n );\n const unSelectedUrlCategoryRowKey =\n selectionState?.categorySelectedRowKeys?.filter(\n category => category !== selectedUrlCategory?.key\n );\n\n if (selected) {\n let payload = {\n ...selectionState,\n urlSelectedRowKeys: [\n ...selectionState?.urlSelectedRowKeys,\n record.key,\n ],\n urlSelected: [...selectionState?.urlSelected, record.url],\n };\n payload.categorySelected.push(selectedUrlCategory.name);\n payload.categorySelectedRowKeys.push(selectedUrlCategory.key);\n payload = {\n ...payload,\n categorySelected: [...new Set(payload.categorySelected)],\n categorySelectedRowKeys: [\n ...new Set(payload.categorySelectedRowKeys),\n ],\n };\n\n dispatch({\n type: SET_WEBSITE_SELECTED_PAGES,\n payload,\n });\n } else {\n const categorySelectedUrlCount = extractCategoryCountSelected(\n selectedUrlCategory?.name,\n selectionState?.urlSelected?.filter(state => state !== record.url),\n categoryTableSource\n );\n\n const payload = {\n ...selectionState,\n categorySelected:\n categorySelectedUrlCount > 0\n ? [...unSelectedUrlCategory, selectedUrlCategory.name]\n : unSelectedUrlCategory,\n categorySelectedRowKeys:\n categorySelectedUrlCount > 0\n ? [...unSelectedUrlCategoryRowKey, selectedUrlCategory.key]\n : unSelectedUrlCategoryRowKey,\n urlSelectedRowKeys: selectionState?.urlSelectedRowKeys?.filter(\n state => state !== record.key\n ),\n urlSelected: selectionState?.urlSelected?.filter(\n state => state !== record.url\n ),\n };\n\n dispatch({\n type: SET_WEBSITE_SELECTED_PAGES,\n payload,\n });\n }\n }\n },\n };\n};\n\nexport const extractWebsiteScanList = scanList => {\n return scanList?.map((scannedUrl, idx) => {\n return {\n key: idx,\n url: scannedUrl,\n filterMethod: 'exactString',\n };\n });\n};\n\nexport const extractAnswerList = (answer, isBotOpenAIEnabled) =>\n orderBy(extractAnswerData(answer, isBotOpenAIEnabled), 'lastEdited', 'desc');\n","import moment from 'moment';\n\nexport const getLastWeeksDate = () => {\n const now = new Date();\n\n return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);\n};\n\nexport const getLastWeekISODate = () => {\n const now = new Date();\n\n return new Date(\n new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7).setHours(\n 0,\n 0,\n 0,\n 0\n )\n ).toISOString();\n};\n\nexport const UTCToLocal = date => {\n const newDate = new Date(\n date.getTime() - date.getTimezoneOffset() * 60 * 1000\n );\n return newDate;\n};\n\nexport const getLocalTimeString = date =>\n date.toLocaleString('en-US', {\n month: 'long',\n day: '2-digit',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n\nexport const getTimeDifference = (\n startDate,\n endDate = new Date(startDate + 'Z'),\n differenceBy = 'day'\n) => {\n const timeDiff = differenceBy.toLowerCase().replace(/s$/, '');\n if (!startDate) {\n return null;\n }\n\n const now = new Date();\n const time = endDate;\n const diff = (now.getTime() - time.getTime()) / 1000;\n const diffMin = Math.round(diff / 60);\n const diffHours = Math.round(diff / 3600);\n const diffDays = Math.round(diff / (3600 * 24));\n\n if (timeDiff.toLowerCase() === 'second' || diff < 60) {\n return `seconds ago`;\n } else if (timeDiff.toLowerCase() === 'minute' || diffMin < 60) {\n return `${diffMin}m ago`;\n } else if (timeDiff.toLowerCase() === 'hour' || diffHours < 24) {\n return `${diffHours}h ago`;\n } else {\n return `${diffDays}d ago`;\n }\n};\n\nexport const isDateValid = input => {\n try {\n if (moment(input).isValid()) {\n return true;\n }\n return false;\n } catch (error) {\n return false;\n }\n};\n\nexport const convertToEndOfDay = date => {\n if (!moment(date).isValid()) {\n return 'Invalid Date';\n }\n return new Date(new Date(date).setHours(23, 59, 59, 59)).toISOString();\n};\n\nexport const convertToStartOfDay = date => {\n if (!moment(date).isValid()) {\n return 'Invalid Date';\n }\n return new Date(new Date(date).setHours(0, 0, 0, 0)).toISOString();\n};\n","export const DEFAULT_BOT_MODE_ON_BOTMODAL = 'openai';\nexport const DEFAULT_BOT_MODE = 'zeroshot';\nexport const BOT_MODES = {\n ZSB: 'zeroshot',\n OPENAI: 'openai',\n};\nexport const DEFAULT_ANSWER_THRESHOLD = 0.1;\n\nexport const DEFAULT_SCRAPE_PREVIEW_EXPRESSION = `()=>{\n function replaceStyle(e,t){\n style_str=\\\"\\\",cssRules=t.cssRules;\n for(let e=0,t=cssRules.length;e{e.setAttribute(\\\"scraped\\\",\\\"\\\"),e.alt=\\\"Not Available!\\\"}),[...document.styleSheets].forEach(e=>{if(owner=e.ownerNode,e.href&&\\\"LINK\\\"==owner.tagName)try{replaceStyle(owner,e)}catch(t){link=document.createElement(\\\"link\\\"),link.type=\\\"text\\/css\\\",link.rel=\\\"stylesheet\\\",link.crossOrigin=\\\"anonymous\\\",link.onload=(()=>{replaceStyle(e.ownerNode,link.sheet),link.remove()}),link.href=e.href,owner.parentNode.insertBefore(link,owner)}}),document.querySelectorAll(\\\"script\\\").forEach(e=>{e.remove()}),document.querySelectorAll(\\\"a\\\").forEach(e=>{e.setAttribute(\\\"onclick\\\",\\\"event.preventDefault()\\\")}),style=document.createElement(\\\"style\\\"),style.innerHTML=\\\"*[scraped-hover] {outline: 2px dashed #ffc800!important;box-shadow: inset 0 0 10px #ffc800!important;}\\\\n*[scraped-selected] {outline: 2px solid red!important;box-shadow: inset 0 0 10px red!important;}\\\\nimg[scraped] {box-shadow: inset 0px 0px 20px -5px #000000!important;background-color: #ffffff80!important;text-align: left;font-family: fantasy;color: red;}\\\",scr=document.createElement(\\\"script\\\"),scr.text='function multiple(e){return document.querySelectorAll(e).length>1||!e.startsWith(\\\"html\\\")&&!e.startsWith(\\\"body\\\")&&!e.startsWith(\\\"#\\\")}function getter(e){var t;return t=e.id?\\\"#\\\"+e.id:e.tagName.toLowerCase()+(e.classList.length>0?\\\".\\\"+[...e.classList].join(\\\".\\\"):\\\"\\\"),[e.parentElement,e,t]}function append_nth_of_type(e,t,r){var a=[...e.querySelectorAll(t)],o=a.length;if(o>1)for(let e=0;e{var t,r=e.target;if(document.querySelectorAll(\\\"*[scraped-selected]\\\").forEach(e=>e.removeAttribute(\\\"scraped-selected\\\")),r.setAttribute(\\\"scraped-selected\\\",\\\"\\\"),r.id)t=\\\"#\\\"+r.id;else{var[a,o,n]=getter(r);if(multiple(n))for(n=append_nth_of_type(a,n,o),t=n;multiple(t);)[a,o,n]=getter(a),n=append_nth_of_type(a,n,o),t=n+\\\" > \\\"+t;else t=n}window.top.postMessage({type:\\\"iframe-scraper-target\\\",target:t},\\\"*\\\"),e.stopPropagation()}),document.addEventListener(\\\"mouseover\\\",e=>{e.target.setAttribute(\\\"scraped-hover\\\",\\\"\\\")}),document.addEventListener(\\\"mouseout\\\",e=>{e.target.removeAttribute(\\\"scraped-hover\\\")});',document.head.appendChild(style),document.body.appendChild(scr);}`;\n"],"sourceRoot":""}