chore: Display Agent Bot token after creation (#11488)
This PR includes: - Displaying the Agent Bot token after creation - Updating the avatar icon when an avatar image is not present Fixes: https://linear.app/chatwoot/issue/CW-4337/agent-bot-token-not-visible
This commit is contained in:
@@ -59,6 +59,11 @@
|
|||||||
"ERROR_MESSAGE": "Could not update bot. Please try again."
|
"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": {
|
"FORM": {
|
||||||
"AVATAR": {
|
"AVATAR": {
|
||||||
"LABEL": "Bot avatar"
|
"LABEL": "Bot avatar"
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ import { useAlert } from 'dashboard/composables';
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { required, helpers, url } from '@vuelidate/validators';
|
import { required, helpers, url } from '@vuelidate/validators';
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
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 Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
import Input from 'dashboard/components-next/input/Input.vue';
|
import Input from 'dashboard/components-next/input/Input.vue';
|
||||||
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
|
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
|
||||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||||
|
import AccessToken from 'dashboard/routes/dashboard/settings/profile/AccessToken.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
@@ -42,6 +45,9 @@ const formState = reactive({
|
|||||||
botAvatarUrl: '',
|
botAvatarUrl: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [showAccessToken, toggleAccessToken] = useToggle();
|
||||||
|
const accessToken = ref('');
|
||||||
|
|
||||||
const v$ = useVuelidate(
|
const v$ = useVuelidate(
|
||||||
{
|
{
|
||||||
botName: {
|
botName: {
|
||||||
@@ -70,11 +76,22 @@ const isLoading = computed(() =>
|
|||||||
: uiFlags.value.isUpdating
|
: uiFlags.value.isUpdating
|
||||||
);
|
);
|
||||||
|
|
||||||
const dialogTitle = computed(() =>
|
const dialogTitle = computed(() => {
|
||||||
props.type === MODAL_TYPES.CREATE
|
if (showAccessToken.value) {
|
||||||
|
return t('AGENT_BOTS.ACCESS_TOKEN.TITLE');
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.type === MODAL_TYPES.CREATE
|
||||||
? t('AGENT_BOTS.ADD.TITLE')
|
? 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(() =>
|
const confirmButtonLabel = computed(() =>
|
||||||
props.type === MODAL_TYPES.CREATE
|
props.type === MODAL_TYPES.CREATE
|
||||||
@@ -90,6 +107,13 @@ const botUrlError = computed(() =>
|
|||||||
v$.value.botUrl.$error ? v$.value.botUrl.$errors[0]?.$message : ''
|
v$.value.botUrl.$error ? v$.value.botUrl.$errors[0]?.$message : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showAccessTokenInput = computed(
|
||||||
|
() =>
|
||||||
|
showAccessToken.value ||
|
||||||
|
props.type === MODAL_TYPES.EDIT ||
|
||||||
|
accessToken.value
|
||||||
|
);
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
Object.assign(formState, {
|
Object.assign(formState, {
|
||||||
botName: '',
|
botName: '',
|
||||||
@@ -128,6 +152,7 @@ const handleAvatarDelete = async () => {
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
v$.value.$touch();
|
v$.value.$touch();
|
||||||
if (v$.value.$invalid) return;
|
if (v$.value.$invalid) return;
|
||||||
|
if (showAccessToken.value) return;
|
||||||
|
|
||||||
const botData = {
|
const botData = {
|
||||||
name: formState.botName,
|
name: formState.botName,
|
||||||
@@ -144,7 +169,7 @@ const handleSubmit = async () => {
|
|||||||
? botData
|
? botData
|
||||||
: { id: props.selectedBot.id, data: botData };
|
: { id: props.selectedBot.id, data: botData };
|
||||||
|
|
||||||
await store.dispatch(
|
const response = await store.dispatch(
|
||||||
`agentBots/${isCreate ? 'create' : 'update'}`,
|
`agentBots/${isCreate ? 'create' : 'update'}`,
|
||||||
actionPayload
|
actionPayload
|
||||||
);
|
);
|
||||||
@@ -154,7 +179,21 @@ const handleSubmit = async () => {
|
|||||||
: t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE');
|
: t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE');
|
||||||
useAlert(alertKey);
|
useAlert(alertKey);
|
||||||
|
|
||||||
|
// 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();
|
dialogRef.value.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialogRef.value.close();
|
||||||
|
}
|
||||||
|
|
||||||
resetForm();
|
resetForm();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorKey = isCreate
|
const errorKey = isCreate
|
||||||
@@ -166,17 +205,43 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
const initializeForm = () => {
|
const initializeForm = () => {
|
||||||
if (props.selectedBot && Object.keys(props.selectedBot).length) {
|
if (props.selectedBot && Object.keys(props.selectedBot).length) {
|
||||||
const { name, description, outgoing_url, thumbnail, bot_config } =
|
const {
|
||||||
props.selectedBot;
|
name,
|
||||||
|
description,
|
||||||
|
outgoing_url: botUrl,
|
||||||
|
thumbnail,
|
||||||
|
bot_config: botConfig,
|
||||||
|
access_token: botAccessToken,
|
||||||
|
} = props.selectedBot;
|
||||||
formState.botName = name || '';
|
formState.botName = name || '';
|
||||||
formState.botDescription = description || '';
|
formState.botDescription = description || '';
|
||||||
formState.botUrl = outgoing_url || bot_config?.webhook_url || '';
|
formState.botUrl = botUrl || botConfig?.webhook_url || '';
|
||||||
formState.botAvatarUrl = thumbnail || '';
|
formState.botAvatarUrl = thumbnail || '';
|
||||||
|
|
||||||
|
if (botAccessToken && props.type === MODAL_TYPES.EDIT) {
|
||||||
|
accessToken.value = botAccessToken;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resetForm();
|
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 });
|
watch(() => props.selectedBot, initializeForm, { immediate: true, deep: true });
|
||||||
|
|
||||||
defineExpose({ dialogRef });
|
defineExpose({ dialogRef });
|
||||||
@@ -187,11 +252,16 @@ defineExpose({ dialogRef });
|
|||||||
ref="dialogRef"
|
ref="dialogRef"
|
||||||
type="edit"
|
type="edit"
|
||||||
:title="dialogTitle"
|
:title="dialogTitle"
|
||||||
|
:description="dialogDescription"
|
||||||
:show-cancel-button="false"
|
:show-cancel-button="false"
|
||||||
:show-confirm-button="false"
|
:show-confirm-button="false"
|
||||||
@close="v$.$reset()"
|
@close="closeModal"
|
||||||
>
|
>
|
||||||
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
||||||
|
<div
|
||||||
|
v-if="!showAccessToken || type === MODAL_TYPES.EDIT"
|
||||||
|
class="flex flex-col gap-4"
|
||||||
|
>
|
||||||
<div class="mb-2 flex flex-col items-start">
|
<div class="mb-2 flex flex-col items-start">
|
||||||
<span class="mb-2 text-sm font-medium text-n-slate-12">
|
<span class="mb-2 text-sm font-medium text-n-slate-12">
|
||||||
{{ $t('AGENT_BOTS.FORM.AVATAR.LABEL') }}
|
{{ $t('AGENT_BOTS.FORM.AVATAR.LABEL') }}
|
||||||
@@ -201,12 +271,14 @@ defineExpose({ dialogRef });
|
|||||||
:name="formState.botName"
|
:name="formState.botName"
|
||||||
:size="68"
|
:size="68"
|
||||||
allow-upload
|
allow-upload
|
||||||
|
icon-name="i-lucide-bot-message-square"
|
||||||
@upload="handleImageUpload"
|
@upload="handleImageUpload"
|
||||||
@delete="handleAvatarDelete"
|
@delete="handleAvatarDelete"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
id="bot-name"
|
||||||
v-model="formState.botName"
|
v-model="formState.botName"
|
||||||
:label="$t('AGENT_BOTS.FORM.NAME.LABEL')"
|
:label="$t('AGENT_BOTS.FORM.NAME.LABEL')"
|
||||||
:placeholder="$t('AGENT_BOTS.FORM.NAME.PLACEHOLDER')"
|
:placeholder="$t('AGENT_BOTS.FORM.NAME.PLACEHOLDER')"
|
||||||
@@ -216,12 +288,14 @@ defineExpose({ dialogRef });
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<TextArea
|
<TextArea
|
||||||
|
id="bot-description"
|
||||||
v-model="formState.botDescription"
|
v-model="formState.botDescription"
|
||||||
:label="$t('AGENT_BOTS.FORM.DESCRIPTION.LABEL')"
|
:label="$t('AGENT_BOTS.FORM.DESCRIPTION.LABEL')"
|
||||||
:placeholder="$t('AGENT_BOTS.FORM.DESCRIPTION.PLACEHOLDER')"
|
:placeholder="$t('AGENT_BOTS.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
id="bot-url"
|
||||||
v-model="formState.botUrl"
|
v-model="formState.botUrl"
|
||||||
:label="$t('AGENT_BOTS.FORM.WEBHOOK_URL.LABEL')"
|
:label="$t('AGENT_BOTS.FORM.WEBHOOK_URL.LABEL')"
|
||||||
:placeholder="$t('AGENT_BOTS.FORM.WEBHOOK_URL.PLACEHOLDER')"
|
:placeholder="$t('AGENT_BOTS.FORM.WEBHOOK_URL.PLACEHOLDER')"
|
||||||
@@ -229,6 +303,17 @@ defineExpose({ dialogRef });
|
|||||||
:message-type="botUrlError ? 'error' : 'info'"
|
:message-type="botUrlError ? 'error' : 'info'"
|
||||||
@blur="v$.botUrl.$touch()"
|
@blur="v$.botUrl.$touch()"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showAccessTokenInput" class="flex flex-col gap-1">
|
||||||
|
<label
|
||||||
|
v-if="type === MODAL_TYPES.EDIT"
|
||||||
|
class="mb-0.5 text-sm font-medium text-n-slate-12"
|
||||||
|
>
|
||||||
|
{{ $t('AGENT_BOTS.ACCESS_TOKEN.TITLE') }}
|
||||||
|
</label>
|
||||||
|
<AccessToken :value="accessToken" @on-copy="onCopyToken" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end w-full gap-2 px-0 py-2">
|
<div class="flex items-center justify-end w-full gap-2 px-0 py-2">
|
||||||
<NextButton
|
<NextButton
|
||||||
@@ -236,9 +321,10 @@ defineExpose({ dialogRef });
|
|||||||
slate
|
slate
|
||||||
type="reset"
|
type="reset"
|
||||||
:label="$t('AGENT_BOTS.FORM.CANCEL')"
|
:label="$t('AGENT_BOTS.FORM.CANCEL')"
|
||||||
@click="dialogRef.close()"
|
@click="onClickClose()"
|
||||||
/>
|
/>
|
||||||
<NextButton
|
<NextButton
|
||||||
|
v-if="!showAccessToken"
|
||||||
type="submit"
|
type="submit"
|
||||||
data-testid="label-submit"
|
data-testid="label-submit"
|
||||||
:label="confirmButtonLabel"
|
:label="confirmButtonLabel"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const onClick = () => {
|
|||||||
<template #masked>
|
<template #masked>
|
||||||
<button
|
<button
|
||||||
class="absolute top-1.5 ltr:right-0.5 rtl:left-0.5"
|
class="absolute top-1.5 ltr:right-0.5 rtl:left-0.5"
|
||||||
|
type="button"
|
||||||
@click="toggleMasked"
|
@click="toggleMasked"
|
||||||
>
|
>
|
||||||
<fluent-icon :icon="maskIcon" :size="16" />
|
<fluent-icon :icon="maskIcon" :size="16" />
|
||||||
@@ -46,7 +47,7 @@ const onClick = () => {
|
|||||||
</template>
|
</template>
|
||||||
</woot-input>
|
</woot-input>
|
||||||
<FormButton
|
<FormButton
|
||||||
type="submit"
|
type="button"
|
||||||
size="large"
|
size="large"
|
||||||
icon="text-copy"
|
icon="text-copy"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
Reference in New Issue
Block a user