diff --git a/app/javascript/dashboard/i18n/locale/en/agentBots.json b/app/javascript/dashboard/i18n/locale/en/agentBots.json index 128cd1564..194cfb999 100644 --- a/app/javascript/dashboard/i18n/locale/en/agentBots.json +++ b/app/javascript/dashboard/i18n/locale/en/agentBots.json @@ -59,6 +59,11 @@ "ERROR_MESSAGE": "Could not update bot. Please try again." } }, + "ACCESS_TOKEN": { + "TITLE": "Access Token", + "DESCRIPTION": "Copy the access token and save it securely", + "COPY_SUCCESSFUL": "Access token copied to clipboard" + }, "FORM": { "AVATAR": { "LABEL": "Bot avatar" diff --git a/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue b/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue index b64b1ef58..97629992a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue @@ -5,12 +5,15 @@ import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; import { required, helpers, url } from '@vuelidate/validators'; import { useVuelidate } from '@vuelidate/core'; +import { copyTextToClipboard } from 'shared/helpers/clipboard'; +import { useToggle } from '@vueuse/core'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import NextButton from 'dashboard/components-next/button/Button.vue'; import Input from 'dashboard/components-next/input/Input.vue'; import TextArea from 'dashboard/components-next/textarea/TextArea.vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; +import AccessToken from 'dashboard/routes/dashboard/settings/profile/AccessToken.vue'; const props = defineProps({ type: { @@ -42,6 +45,9 @@ const formState = reactive({ botAvatarUrl: '', }); +const [showAccessToken, toggleAccessToken] = useToggle(); +const accessToken = ref(''); + const v$ = useVuelidate( { botName: { @@ -70,11 +76,22 @@ const isLoading = computed(() => : uiFlags.value.isUpdating ); -const dialogTitle = computed(() => - props.type === MODAL_TYPES.CREATE +const dialogTitle = computed(() => { + if (showAccessToken.value) { + return t('AGENT_BOTS.ACCESS_TOKEN.TITLE'); + } + + return props.type === MODAL_TYPES.CREATE ? t('AGENT_BOTS.ADD.TITLE') - : t('AGENT_BOTS.EDIT.TITLE') -); + : t('AGENT_BOTS.EDIT.TITLE'); +}); + +const dialogDescription = computed(() => { + if (showAccessToken.value) { + return t('AGENT_BOTS.ACCESS_TOKEN.DESCRIPTION'); + } + return ''; +}); const confirmButtonLabel = computed(() => props.type === MODAL_TYPES.CREATE @@ -90,6 +107,13 @@ const botUrlError = computed(() => v$.value.botUrl.$error ? v$.value.botUrl.$errors[0]?.$message : '' ); +const showAccessTokenInput = computed( + () => + showAccessToken.value || + props.type === MODAL_TYPES.EDIT || + accessToken.value +); + const resetForm = () => { Object.assign(formState, { botName: '', @@ -128,6 +152,7 @@ const handleAvatarDelete = async () => { const handleSubmit = async () => { v$.value.$touch(); if (v$.value.$invalid) return; + if (showAccessToken.value) return; const botData = { name: formState.botName, @@ -144,7 +169,7 @@ const handleSubmit = async () => { ? botData : { id: props.selectedBot.id, data: botData }; - await store.dispatch( + const response = await store.dispatch( `agentBots/${isCreate ? 'create' : 'update'}`, actionPayload ); @@ -154,7 +179,21 @@ const handleSubmit = async () => { : t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE'); useAlert(alertKey); - dialogRef.value.close(); + // Show access token after creation + if (isCreate) { + const { access_token: responseAccessToken, id } = response || {}; + + if (id && responseAccessToken) { + accessToken.value = responseAccessToken; + toggleAccessToken(true); + } else { + accessToken.value = ''; + dialogRef.value.close(); + } + } else { + dialogRef.value.close(); + } + resetForm(); } catch (error) { const errorKey = isCreate @@ -166,17 +205,43 @@ const handleSubmit = async () => { const initializeForm = () => { if (props.selectedBot && Object.keys(props.selectedBot).length) { - const { name, description, outgoing_url, thumbnail, bot_config } = - props.selectedBot; + const { + name, + description, + outgoing_url: botUrl, + thumbnail, + bot_config: botConfig, + access_token: botAccessToken, + } = props.selectedBot; formState.botName = name || ''; formState.botDescription = description || ''; - formState.botUrl = outgoing_url || bot_config?.webhook_url || ''; + formState.botUrl = botUrl || botConfig?.webhook_url || ''; formState.botAvatarUrl = thumbnail || ''; + + if (botAccessToken && props.type === MODAL_TYPES.EDIT) { + accessToken.value = botAccessToken; + } } else { resetForm(); } }; +const onCopyToken = async value => { + await copyTextToClipboard(value); + useAlert(t('COMPONENTS.CODE.COPY_SUCCESSFUL')); +}; + +const closeModal = () => { + if (!showAccessToken.value) v$.value?.$reset(); + accessToken.value = ''; + toggleAccessToken(false); +}; + +const onClickClose = () => { + closeModal(); + dialogRef.value.close(); +}; + watch(() => props.selectedBot, initializeForm, { immediate: true, deep: true }); defineExpose({ dialogRef }); @@ -187,48 +252,68 @@ defineExpose({ dialogRef }); ref="dialogRef" type="edit" :title="dialogTitle" + :description="dialogDescription" :show-cancel-button="false" :show-confirm-button="false" - @close="v$.$reset()" + @close="closeModal" >