Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df0e8cd8b | ||
|
|
687fd71814 | ||
|
|
8741c80fd0 | ||
|
|
a3dcc38a2c | ||
|
|
0de7149a68 | ||
|
|
504b69a1b2 | ||
|
|
877d4a4339 | ||
|
|
39211ac75d | ||
|
|
30f1a247db | ||
|
|
5d8406ef4f | ||
|
|
504b65bdac | ||
|
|
d97dd00b77 | ||
|
|
c3c5cf5bd6 | ||
|
|
db091b54b7 | ||
|
|
8bfcf216c1 | ||
|
|
64111995ac |
@@ -44,9 +44,8 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@account.assign_attributes(account_params.slice(:name, :locale, :domain, :support_email))
|
@account.assign_attributes(account_params.slice(:name, :locale, :domain, :support_email, :auto_resolve_duration))
|
||||||
@account.custom_attributes.merge!(custom_attributes_params)
|
@account.custom_attributes.merge!(custom_attributes_params)
|
||||||
@account.settings.merge!(settings_params)
|
|
||||||
@account.custom_attributes['onboarding_step'] = 'invite_team' if @account.custom_attributes['onboarding_step'] == 'account_update'
|
@account.custom_attributes['onboarding_step'] = 'invite_team' if @account.custom_attributes['onboarding_step'] == 'account_update'
|
||||||
@account.save!
|
@account.save!
|
||||||
end
|
end
|
||||||
@@ -84,17 +83,13 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :user_full_name)
|
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_attributes_params
|
def custom_attributes_params
|
||||||
params.permit(:industry, :company_size, :timezone)
|
params.permit(:industry, :company_size, :timezone)
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings_params
|
|
||||||
params.permit(:auto_resolve_after, :auto_resolve_message)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_signup_enabled
|
def check_signup_enabled
|
||||||
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
|
raise ActionController::RoutingError, 'Not Found' if GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false') == 'false'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Api::V2::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :user_full_name)
|
params.permit(:account_name, :email, :name, :password, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_signup_enabled
|
def check_signup_enabled
|
||||||
|
|||||||
+2
-4
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
import { useCaptain } from 'dashboard/composables/useCaptain';
|
import { useCaptain } from 'dashboard/composables/useCaptain';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -9,7 +9,7 @@ import Banner from 'dashboard/components-next/banner/Banner.vue';
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { accountId } = useAccount();
|
const { accountId } = useAccount();
|
||||||
|
|
||||||
const { documentLimits, fetchLimits } = useCaptain();
|
const { documentLimits } = useCaptain();
|
||||||
|
|
||||||
const openBilling = () => {
|
const openBilling = () => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -24,8 +24,6 @@ const showBanner = computed(() => {
|
|||||||
const { currentAvailable } = documentLimits.value;
|
const { currentAvailable } = documentLimits.value;
|
||||||
return currentAvailable === 0;
|
return currentAvailable === 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(fetchLimits);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
+2
-4
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
import { useCaptain } from 'dashboard/composables/useCaptain';
|
import { useCaptain } from 'dashboard/composables/useCaptain';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -9,7 +9,7 @@ import Banner from 'dashboard/components-next/banner/Banner.vue';
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { accountId } = useAccount();
|
const { accountId } = useAccount();
|
||||||
|
|
||||||
const { responseLimits, fetchLimits } = useCaptain();
|
const { responseLimits } = useCaptain();
|
||||||
|
|
||||||
const openBilling = () => {
|
const openBilling = () => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -26,8 +26,6 @@ const showBanner = computed(() => {
|
|||||||
|
|
||||||
return consumed / totalCount > 0.8;
|
return consumed / totalCount > 0.8;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(fetchLimits);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import Input from './Input.vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
min: { type: Number, default: 0 },
|
|
||||||
max: { type: Number, default: Infinity },
|
|
||||||
disabled: { type: Boolean, default: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const duration = defineModel('modelValue', { type: Number, default: null });
|
|
||||||
|
|
||||||
const UNIT_TYPES = {
|
|
||||||
MINUTES: 'minutes',
|
|
||||||
HOURS: 'hours',
|
|
||||||
DAYS: 'days',
|
|
||||||
};
|
|
||||||
const unit = ref(UNIT_TYPES.MINUTES);
|
|
||||||
|
|
||||||
const transformedValue = computed({
|
|
||||||
get() {
|
|
||||||
if (unit.value === UNIT_TYPES.MINUTES) return duration.value;
|
|
||||||
if (unit.value === UNIT_TYPES.HOURS) return Math.floor(duration.value / 60);
|
|
||||||
if (unit.value === UNIT_TYPES.DAYS)
|
|
||||||
return Math.floor(duration.value / 24 / 60);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
let minuteValue;
|
|
||||||
if (unit.value === UNIT_TYPES.MINUTES) {
|
|
||||||
minuteValue = Math.floor(newValue);
|
|
||||||
} else if (unit.value === UNIT_TYPES.HOURS) {
|
|
||||||
minuteValue = Math.floor(newValue * 60);
|
|
||||||
} else if (unit.value === UNIT_TYPES.DAYS) {
|
|
||||||
minuteValue = Math.floor(newValue * 24 * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
duration.value = Math.min(Math.max(minuteValue, props.min), props.max);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Input
|
|
||||||
v-model="transformedValue"
|
|
||||||
type="number"
|
|
||||||
autocomplete="off"
|
|
||||||
:disabled="disabled"
|
|
||||||
:placeholder="t('DURATION_INPUT.PLACEHOLDER')"
|
|
||||||
class="flex-grow w-full disabled:"
|
|
||||||
/>
|
|
||||||
<select
|
|
||||||
v-model="unit"
|
|
||||||
:disabled="disabled"
|
|
||||||
class="mb-0 text-sm disabled:outline-n-weak disabled:opacity-40"
|
|
||||||
>
|
|
||||||
<option :value="UNIT_TYPES.MINUTES">
|
|
||||||
{{ t('DURATION_INPUT.MINUTES') }}
|
|
||||||
</option>
|
|
||||||
<option :value="UNIT_TYPES.HOURS">{{ t('DURATION_INPUT.HOURS') }}</option>
|
|
||||||
<option :value="UNIT_TYPES.DAYS">{{ t('DURATION_INPUT.DAYS') }}</option>
|
|
||||||
</select>
|
|
||||||
</template>
|
|
||||||
@@ -27,10 +27,10 @@ const updateValue = () => {
|
|||||||
>
|
>
|
||||||
<span class="sr-only">{{ t('SWITCH.TOGGLE') }}</span>
|
<span class="sr-only">{{ t('SWITCH.TOGGLE') }}</span>
|
||||||
<span
|
<span
|
||||||
class="absolute top-0.5 left-0.5 h-3 w-3 transform rounded-full shadow-sm transition-transform duration-200 ease-in-out"
|
class="absolute top-[0.07rem] left-0.5 h-3 w-3 transform rounded-full shadow-sm transition-transform duration-200 ease-in-out"
|
||||||
:class="
|
:class="
|
||||||
modelValue
|
modelValue
|
||||||
? 'translate-x-3 bg-white'
|
? 'translate-x-2.5 bg-white'
|
||||||
: 'translate-x-0 bg-white dark:bg-n-black'
|
: 'translate-x-0 bg-white dark:bg-n-black'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useMapGetter, useStore } from './store';
|
import { useMapGetter } from './store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable for account-related operations.
|
* Composable for account-related operations.
|
||||||
@@ -12,7 +12,6 @@ export function useAccount() {
|
|||||||
* @type {import('vue').ComputedRef<number>}
|
* @type {import('vue').ComputedRef<number>}
|
||||||
*/
|
*/
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
|
||||||
const getAccountFn = useMapGetter('accounts/getAccount');
|
const getAccountFn = useMapGetter('accounts/getAccount');
|
||||||
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
|
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
|
||||||
const isFeatureEnabledonAccount = useMapGetter(
|
const isFeatureEnabledonAccount = useMapGetter(
|
||||||
@@ -45,12 +44,6 @@ export function useAccount() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAccount = async data => {
|
|
||||||
await store.dispatch('accounts/update', {
|
|
||||||
...data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
route,
|
route,
|
||||||
@@ -59,6 +52,5 @@ export function useAccount() {
|
|||||||
accountScopedRoute,
|
accountScopedRoute,
|
||||||
isCloudFeatureEnabled,
|
isCloudFeatureEnabled,
|
||||||
isOnChatwootCloud,
|
isOnChatwootCloud,
|
||||||
updateAccount,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from 'dashboard/composables/store.js';
|
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||||
|
|
||||||
export function useCaptain() {
|
export function useCaptain() {
|
||||||
const store = useStore();
|
|
||||||
const { isCloudFeatureEnabled, currentAccount } = useAccount();
|
const { isCloudFeatureEnabled, currentAccount } = useAccount();
|
||||||
|
|
||||||
const captainEnabled = computed(() => {
|
const captainEnabled = computed(() => {
|
||||||
@@ -32,15 +30,10 @@ export function useCaptain() {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchLimits = () => {
|
|
||||||
store.dispatch('accounts/limits');
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
captainEnabled,
|
captainEnabled,
|
||||||
captainLimits,
|
captainLimits,
|
||||||
documentLimits,
|
documentLimits,
|
||||||
responseLimits,
|
responseLimits,
|
||||||
fetchLimits,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "الاسم",
|
"LABEL": "الاسم",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "الوصف",
|
"LABEL": "الوصف",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Име",
|
"LABEL": "Име",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Описание",
|
"LABEL": "Описание",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nom",
|
"LABEL": "Nom",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descripció",
|
"LABEL": "Descripció",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Název",
|
"LABEL": "Název",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Navn",
|
"LABEL": "Navn",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Beskrivelse",
|
"LABEL": "Beskrivelse",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Beschreibung",
|
"LABEL": "Beschreibung",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Όνομα",
|
"LABEL": "Όνομα",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Περιγραφή",
|
"LABEL": "Περιγραφή",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -43,11 +43,5 @@
|
|||||||
"FEATURE_SPOTLIGHT": {
|
"FEATURE_SPOTLIGHT": {
|
||||||
"LEARN_MORE": "Learn more",
|
"LEARN_MORE": "Learn more",
|
||||||
"WATCH_VIDEO": "Watch video"
|
"WATCH_VIDEO": "Watch video"
|
||||||
},
|
|
||||||
"DURATION_INPUT": {
|
|
||||||
"MINUTES": "Minutes",
|
|
||||||
"HOURS": "Hours",
|
|
||||||
"DAYS": "Days",
|
|
||||||
"PLACEHOLDER": "Enter duration"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,6 @@
|
|||||||
"TITLE": "Account ID",
|
"TITLE": "Account ID",
|
||||||
"NOTE": "This ID is required if you are building an API based integration"
|
"NOTE": "This ID is required if you are building an API based integration"
|
||||||
},
|
},
|
||||||
"AUTO_RESOLVE": {
|
|
||||||
"TITLE": "Auto-resolve conversations",
|
|
||||||
"NOTE": "This configuration would allow you to automatically end the conversation after a certain period. Set the duration and customize the message to the user below."
|
|
||||||
},
|
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Account name",
|
"LABEL": "Account name",
|
||||||
"PLACEHOLDER": "Your account name",
|
"PLACEHOLDER": "Your account name",
|
||||||
@@ -69,18 +65,9 @@
|
|||||||
"ERROR": ""
|
"ERROR": ""
|
||||||
},
|
},
|
||||||
"AUTO_RESOLVE_DURATION": {
|
"AUTO_RESOLVE_DURATION": {
|
||||||
"LABEL": "Inactivity duration for resolution",
|
"LABEL": "Number of days after a ticket should auto resolve if there is no activity",
|
||||||
"HELP": "Duration after a conversation should auto resolve if there is no activity",
|
|
||||||
"PLACEHOLDER": "30",
|
"PLACEHOLDER": "30",
|
||||||
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)",
|
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
|
||||||
"API": {
|
|
||||||
"SUCCESS": "Auto resolve settings updated successfully",
|
|
||||||
"ERROR": "Failed to update auto resolve settings"
|
|
||||||
},
|
|
||||||
"UPDATE_BUTTON": "Update Auto-resolve",
|
|
||||||
"MESSAGE_LABEL": "Custom resolution message",
|
|
||||||
"MESSAGE_PLACEHOLDER": "Conversation was marked resolved by system due to 15 days of inactivity",
|
|
||||||
"MESSAGE_HELP": "This message is sent to the customer when a conversation is automatically resolved by the system due to inactivity."
|
|
||||||
},
|
},
|
||||||
"FEATURES": {
|
"FEATURES": {
|
||||||
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nombre",
|
"LABEL": "Nombre",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descripción",
|
"LABEL": "Descripción",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "نام",
|
"LABEL": "نام",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "توضیحات",
|
"LABEL": "توضیحات",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nimi",
|
"LABEL": "Nimi",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Kuvaus",
|
"LABEL": "Kuvaus",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nom",
|
"LABEL": "Nom",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "שם",
|
"LABEL": "שם",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "תיאור",
|
"LABEL": "תיאור",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Ime",
|
"LABEL": "Ime",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Név",
|
"LABEL": "Név",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Leírás",
|
"LABEL": "Leírás",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nama",
|
"LABEL": "Nama",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Deskripsi",
|
"LABEL": "Deskripsi",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nafn",
|
"LABEL": "Nafn",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nome",
|
"LABEL": "Nome",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descrizione",
|
"LABEL": "Descrizione",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "名前",
|
"LABEL": "名前",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "説明",
|
"LABEL": "説明",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "製品名",
|
"LABEL": "製品名",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "製品名が必要です"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "이름",
|
"LABEL": "이름",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "내용",
|
"LABEL": "내용",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Vardas",
|
"LABEL": "Vardas",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Aprašymas",
|
"LABEL": "Aprašymas",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nosaukums",
|
"LABEL": "Nosaukums",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Apraksts",
|
"LABEL": "Apraksts",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Produkta Nosaukums",
|
"LABEL": "Produkta Nosaukums",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "Nepieciešams produkta nosaukums"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "പേര്",
|
"LABEL": "പേര്",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "വിവരണം",
|
"LABEL": "വിവരണം",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nama",
|
"LABEL": "Nama",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Naam",
|
"LABEL": "Naam",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Beschrijving",
|
"LABEL": "Beschrijving",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Navn",
|
"LABEL": "Navn",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Beskrivelse",
|
"LABEL": "Beskrivelse",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Imię",
|
"LABEL": "Imię",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Opis",
|
"LABEL": "Opis",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nome:",
|
"LABEL": "Nome:",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descrição",
|
"LABEL": "Descrição",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nome",
|
"LABEL": "Nome",
|
||||||
"PLACEHOLDER": "Digite o nome do assistente",
|
"PLACEHOLDER": "Digite o nome do assistente"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descrição",
|
"LABEL": "Descrição",
|
||||||
"PLACEHOLDER": "Digite a descrição do assistente",
|
"PLACEHOLDER": "Digite a descrição do assistente"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Nome do Produto",
|
"LABEL": "Nome do Produto",
|
||||||
"PLACEHOLDER": "Digite o nome do produto",
|
"PLACEHOLDER": "Digite o nome do produto"
|
||||||
"ERROR": "O nome do produto é obrigatório"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Mensagem de boas-vindas",
|
"LABEL": "Mensagem de boas-vindas",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Nume",
|
"LABEL": "Nume",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Descriere",
|
"LABEL": "Descriere",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Имя",
|
"LABEL": "Имя",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Описание",
|
"LABEL": "Описание",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Название продукта",
|
"LABEL": "Название продукта",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "Требуется название продукта"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Meno",
|
"LABEL": "Meno",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Ime",
|
"LABEL": "Ime",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Opis",
|
"LABEL": "Opis",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Namn",
|
"LABEL": "Namn",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Beskrivning",
|
"LABEL": "Beskrivning",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "பெயர்",
|
"LABEL": "பெயர்",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "ชื่อ",
|
"LABEL": "ชื่อ",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "คำอธิบาย",
|
"LABEL": "คำอธิบาย",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "İsim",
|
"LABEL": "İsim",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Açıklama",
|
"LABEL": "Açıklama",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Ім'я",
|
"LABEL": "Ім'я",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Опис",
|
"LABEL": "Опис",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "نام",
|
"LABEL": "نام",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Description",
|
"LABEL": "Description",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Tên",
|
"LABEL": "Tên",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "Mô tả",
|
"LABEL": "Mô tả",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "姓名:",
|
"LABEL": "姓名:",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "描述信息",
|
"LABEL": "描述信息",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "产品名称",
|
"LABEL": "产品名称",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "产品名称是必需的"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -406,18 +406,15 @@
|
|||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "姓名",
|
"LABEL": "姓名",
|
||||||
"PLACEHOLDER": "Enter assistant name",
|
"PLACEHOLDER": "Enter assistant name"
|
||||||
"ERROR": "The name is required"
|
|
||||||
},
|
},
|
||||||
"DESCRIPTION": {
|
"DESCRIPTION": {
|
||||||
"LABEL": "描述資訊",
|
"LABEL": "描述資訊",
|
||||||
"PLACEHOLDER": "Enter assistant description",
|
"PLACEHOLDER": "Enter assistant description"
|
||||||
"ERROR": "The description is required"
|
|
||||||
},
|
},
|
||||||
"PRODUCT_NAME": {
|
"PRODUCT_NAME": {
|
||||||
"LABEL": "Product Name",
|
"LABEL": "Product Name",
|
||||||
"PLACEHOLDER": "Enter product name",
|
"PLACEHOLDER": "Enter product name"
|
||||||
"ERROR": "The product name is required"
|
|
||||||
},
|
},
|
||||||
"WELCOME_MESSAGE": {
|
"WELCOME_MESSAGE": {
|
||||||
"LABEL": "Welcome Message",
|
"LABEL": "Welcome Message",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AccountSelector from 'dashboard/components/layout/sidebarComponents/Accou
|
|||||||
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
||||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
||||||
import UpgradePage from 'dashboard/routes/dashboard/upgrade/UpgradePage.vue';
|
import UpgradePage from 'dashboard/routes/dashboard/upgrade/UpgradePage.vue';
|
||||||
|
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||||
|
|
||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
@@ -37,6 +38,7 @@ export default {
|
|||||||
AddLabelModal,
|
AddLabelModal,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
UpgradePage,
|
UpgradePage,
|
||||||
|
Spinner,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const upgradePageRef = ref(null);
|
const upgradePageRef = ref(null);
|
||||||
@@ -64,6 +66,8 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
|
accountUIFlags: 'accounts/getUIFlags',
|
||||||
|
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||||
}),
|
}),
|
||||||
currentRoute() {
|
currentRoute() {
|
||||||
return ' ';
|
return ' ';
|
||||||
@@ -71,13 +75,6 @@ export default {
|
|||||||
showUpgradePage() {
|
showUpgradePage() {
|
||||||
return this.upgradePageRef?.shouldShowUpgradePage;
|
return this.upgradePageRef?.shouldShowUpgradePage;
|
||||||
},
|
},
|
||||||
bypassUpgradePage() {
|
|
||||||
return [
|
|
||||||
'billing_settings_index',
|
|
||||||
'settings_inbox_list',
|
|
||||||
'agent_list',
|
|
||||||
].includes(this.$route.name);
|
|
||||||
},
|
|
||||||
isSidebarOpen() {
|
isSidebarOpen() {
|
||||||
const { show_secondary_sidebar: showSecondarySidebar } = this.uiSettings;
|
const { show_secondary_sidebar: showSecondarySidebar } = this.uiSettings;
|
||||||
return showSecondarySidebar;
|
return showSecondarySidebar;
|
||||||
@@ -116,6 +113,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.fetchLimits();
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
this.$nextTick(this.checkBanner);
|
this.$nextTick(this.checkBanner);
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
@@ -129,6 +127,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
fetchLimits() {
|
||||||
|
if (!this.isOnChatwootCloud) return;
|
||||||
|
this.$store.dispatch('accounts/limits');
|
||||||
|
},
|
||||||
checkBanner() {
|
checkBanner() {
|
||||||
this.hasBanner =
|
this.hasBanner =
|
||||||
document.getElementsByClassName('woot-banner').length > 0;
|
document.getElementsByClassName('woot-banner').length > 0;
|
||||||
@@ -211,39 +213,43 @@ export default {
|
|||||||
@show-add-label-popup="showAddLabelPopup"
|
@show-add-label-popup="showAddLabelPopup"
|
||||||
/>
|
/>
|
||||||
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
<main class="flex flex-1 h-full min-h-0 px-0 overflow-hidden">
|
||||||
<UpgradePage
|
<div
|
||||||
v-show="showUpgradePage"
|
v-if="accountUIFlags.isFetchingLimits"
|
||||||
ref="upgradePageRef"
|
class="flex items-center justify-center w-full h-full"
|
||||||
:bypass-upgrade-page="bypassUpgradePage"
|
>
|
||||||
/>
|
<Spinner :size="24" class="text-n-brand" />
|
||||||
<template v-if="!showUpgradePage">
|
</div>
|
||||||
<router-view />
|
<template v-else>
|
||||||
<CommandBar />
|
<UpgradePage ref="upgradePageRef" />
|
||||||
<NotificationPanel
|
<template v-if="!showUpgradePage">
|
||||||
v-if="isNotificationPanel"
|
<router-view />
|
||||||
@close="closeNotificationPanel"
|
<CommandBar />
|
||||||
|
<NotificationPanel
|
||||||
|
v-if="isNotificationPanel"
|
||||||
|
@close="closeNotificationPanel"
|
||||||
|
/>
|
||||||
|
<woot-modal
|
||||||
|
v-model:show="showAddLabelModal"
|
||||||
|
:on-close="hideAddLabelPopup"
|
||||||
|
>
|
||||||
|
<AddLabelModal @close="hideAddLabelPopup" />
|
||||||
|
</woot-modal>
|
||||||
|
</template>
|
||||||
|
<AccountSelector
|
||||||
|
:show-account-modal="showAccountModal"
|
||||||
|
@close-account-modal="toggleAccountModal"
|
||||||
|
@show-create-account-modal="openCreateAccountModal"
|
||||||
|
/>
|
||||||
|
<AddAccountModal
|
||||||
|
:show="showCreateAccountModal"
|
||||||
|
@close-account-create-modal="closeCreateAccountModal"
|
||||||
|
/>
|
||||||
|
<WootKeyShortcutModal
|
||||||
|
v-model:show="showShortcutModal"
|
||||||
|
@close="closeKeyShortcutModal"
|
||||||
|
@clickaway="closeKeyShortcutModal"
|
||||||
/>
|
/>
|
||||||
<woot-modal
|
|
||||||
v-model:show="showAddLabelModal"
|
|
||||||
:on-close="hideAddLabelPopup"
|
|
||||||
>
|
|
||||||
<AddLabelModal @close="hideAddLabelPopup" />
|
|
||||||
</woot-modal>
|
|
||||||
</template>
|
</template>
|
||||||
<AccountSelector
|
|
||||||
:show-account-modal="showAccountModal"
|
|
||||||
@close-account-modal="toggleAccountModal"
|
|
||||||
@show-create-account-modal="openCreateAccountModal"
|
|
||||||
/>
|
|
||||||
<AddAccountModal
|
|
||||||
:show="showCreateAccountModal"
|
|
||||||
@close-account-create-modal="closeCreateAccountModal"
|
|
||||||
/>
|
|
||||||
<WootKeyShortcutModal
|
|
||||||
v-model:show="showShortcutModal"
|
|
||||||
@close="closeKeyShortcutModal"
|
|
||||||
@clickaway="closeKeyShortcutModal"
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
<script>
|
<script>
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required } from '@vuelidate/validators';
|
import { required, minValue, maxValue } from '@vuelidate/validators';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { useAlert } from 'dashboard/composables';
|
import { useAlert } from 'dashboard/composables';
|
||||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||||
import { useConfig } from 'dashboard/composables/useConfig';
|
import { useConfig } from 'dashboard/composables/useConfig';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
||||||
|
import semver from 'semver';
|
||||||
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
import WithLabel from 'v3/components/Form/WithLabel.vue';
|
|
||||||
import NextInput from 'next/input/Input.vue';
|
|
||||||
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||||
|
import V4Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
import WootConfirmDeleteModal from 'dashboard/components/widgets/modal/ConfirmDeleteModal.vue';
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
import AccountId from './components/AccountId.vue';
|
|
||||||
import BuildInfo from './components/BuildInfo.vue';
|
|
||||||
import AccountDelete from './components/AccountDelete.vue';
|
|
||||||
import AutoResolve from './components/AutoResolve.vue';
|
|
||||||
import SectionLayout from './components/SectionLayout.vue';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseSettingsHeader,
|
BaseSettingsHeader,
|
||||||
|
V4Button,
|
||||||
|
WootConfirmDeleteModal,
|
||||||
NextButton,
|
NextButton,
|
||||||
AccountId,
|
|
||||||
BuildInfo,
|
|
||||||
AccountDelete,
|
|
||||||
AutoResolve,
|
|
||||||
SectionLayout,
|
|
||||||
WithLabel,
|
|
||||||
NextInput,
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { updateUISettings } = useUISettings();
|
const { updateUISettings } = useUISettings();
|
||||||
@@ -46,6 +37,9 @@ export default {
|
|||||||
domain: '',
|
domain: '',
|
||||||
supportEmail: '',
|
supportEmail: '',
|
||||||
features: {},
|
features: {},
|
||||||
|
autoResolveDuration: null,
|
||||||
|
latestChatwootVersion: null,
|
||||||
|
showDeletePopup: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
@@ -55,9 +49,14 @@ export default {
|
|||||||
locale: {
|
locale: {
|
||||||
required,
|
required,
|
||||||
},
|
},
|
||||||
|
autoResolveDuration: {
|
||||||
|
minValue: minValue(1),
|
||||||
|
maxValue: maxValue(999),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
globalConfig: 'globalConfig/get',
|
||||||
getAccount: 'accounts/getAccount',
|
getAccount: 'accounts/getAccount',
|
||||||
uiFlags: 'accounts/getUIFlags',
|
uiFlags: 'accounts/getUIFlags',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
@@ -69,6 +68,16 @@ export default {
|
|||||||
FEATURE_FLAGS.AUTO_RESOLVE_CONVERSATIONS
|
FEATURE_FLAGS.AUTO_RESOLVE_CONVERSATIONS
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
hasAnUpdateAvailable() {
|
||||||
|
if (!semver.valid(this.latestChatwootVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return semver.lt(
|
||||||
|
this.globalConfig.appVersion,
|
||||||
|
this.latestChatwootVersion
|
||||||
|
);
|
||||||
|
},
|
||||||
languagesSortedByCode() {
|
languagesSortedByCode() {
|
||||||
const enabledLanguages = [...this.enabledLanguages];
|
const enabledLanguages = [...this.enabledLanguages];
|
||||||
return enabledLanguages.sort((l1, l2) =>
|
return enabledLanguages.sort((l1, l2) =>
|
||||||
@@ -78,19 +87,51 @@ export default {
|
|||||||
isUpdating() {
|
isUpdating() {
|
||||||
return this.uiFlags.isUpdating;
|
return this.uiFlags.isUpdating;
|
||||||
},
|
},
|
||||||
|
|
||||||
featureInboundEmailEnabled() {
|
featureInboundEmailEnabled() {
|
||||||
return !!this.features?.inbound_emails;
|
return !!this.features?.inbound_emails;
|
||||||
},
|
},
|
||||||
|
|
||||||
featureCustomReplyDomainEnabled() {
|
featureCustomReplyDomainEnabled() {
|
||||||
return (
|
return (
|
||||||
this.featureInboundEmailEnabled && !!this.features.custom_reply_domain
|
this.featureInboundEmailEnabled && !!this.features.custom_reply_domain
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
featureCustomReplyEmailEnabled() {
|
featureCustomReplyEmailEnabled() {
|
||||||
return (
|
return (
|
||||||
this.featureInboundEmailEnabled && !!this.features.custom_reply_email
|
this.featureInboundEmailEnabled && !!this.features.custom_reply_email
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAccountId() {
|
||||||
|
return this.id.toString();
|
||||||
|
},
|
||||||
|
confirmPlaceHolderText() {
|
||||||
|
return `${this.$t(
|
||||||
|
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.PLACE_HOLDER',
|
||||||
|
{
|
||||||
|
accountName: this.name,
|
||||||
|
}
|
||||||
|
)}`;
|
||||||
|
},
|
||||||
|
isMarkedForDeletion() {
|
||||||
|
const { custom_attributes = {} } = this.currentAccount;
|
||||||
|
return !!custom_attributes.marked_for_deletion_at;
|
||||||
|
},
|
||||||
|
markedForDeletionDate() {
|
||||||
|
const { custom_attributes = {} } = this.currentAccount;
|
||||||
|
if (!custom_attributes.marked_for_deletion_at) return null;
|
||||||
|
return new Date(custom_attributes.marked_for_deletion_at);
|
||||||
|
},
|
||||||
|
markedForDeletionReason() {
|
||||||
|
const { custom_attributes = {} } = this.currentAccount;
|
||||||
|
return custom_attributes.marked_for_deletion_reason || 'manual_deletion';
|
||||||
|
},
|
||||||
|
formattedDeletionDate() {
|
||||||
|
if (!this.markedForDeletionDate) return '';
|
||||||
|
return this.markedForDeletionDate.toLocaleString();
|
||||||
|
},
|
||||||
currentAccount() {
|
currentAccount() {
|
||||||
return this.getAccount(this.accountId) || {};
|
return this.getAccount(this.accountId) || {};
|
||||||
},
|
},
|
||||||
@@ -101,8 +142,16 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async initializeAccount() {
|
async initializeAccount() {
|
||||||
try {
|
try {
|
||||||
const { name, locale, id, domain, support_email, features } =
|
const {
|
||||||
this.getAccount(this.accountId);
|
name,
|
||||||
|
locale,
|
||||||
|
id,
|
||||||
|
domain,
|
||||||
|
support_email,
|
||||||
|
features,
|
||||||
|
auto_resolve_duration,
|
||||||
|
latest_chatwoot_version: latestChatwootVersion,
|
||||||
|
} = this.getAccount(this.accountId);
|
||||||
|
|
||||||
this.$root.$i18n.locale = locale;
|
this.$root.$i18n.locale = locale;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -111,6 +160,8 @@ export default {
|
|||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.supportEmail = support_email;
|
this.supportEmail = support_email;
|
||||||
this.features = features;
|
this.features = features;
|
||||||
|
this.autoResolveDuration = auto_resolve_duration;
|
||||||
|
this.latestChatwootVersion = latestChatwootVersion;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
}
|
}
|
||||||
@@ -128,6 +179,7 @@ export default {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
domain: this.domain,
|
domain: this.domain,
|
||||||
support_email: this.supportEmail,
|
support_email: this.supportEmail,
|
||||||
|
auto_resolve_duration: this.autoResolveDuration,
|
||||||
});
|
});
|
||||||
this.$root.$i18n.locale = this.locale;
|
this.$root.$i18n.locale = this.locale;
|
||||||
this.getAccount(this.id).locale = this.locale;
|
this.getAccount(this.id).locale = this.locale;
|
||||||
@@ -144,101 +196,252 @@ export default {
|
|||||||
rtl_view: isRTLSupported,
|
rtl_view: isRTLSupported,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// Delete Function
|
||||||
|
openDeletePopup() {
|
||||||
|
this.showDeletePopup = true;
|
||||||
|
},
|
||||||
|
closeDeletePopup() {
|
||||||
|
this.showDeletePopup = false;
|
||||||
|
},
|
||||||
|
async markAccountForDeletion() {
|
||||||
|
this.closeDeletePopup();
|
||||||
|
try {
|
||||||
|
// Use the enterprise API to toggle deletion with delete action
|
||||||
|
await this.$store.dispatch('accounts/toggleDeletion', {
|
||||||
|
action_type: 'delete',
|
||||||
|
});
|
||||||
|
// Refresh account data
|
||||||
|
await this.$store.dispatch('accounts/get');
|
||||||
|
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SUCCESS'));
|
||||||
|
} catch (error) {
|
||||||
|
// Handle error message
|
||||||
|
this.handleDeletionError(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDeletionError(error) {
|
||||||
|
const errorKey = error.response?.data?.error_key;
|
||||||
|
if (errorKey) {
|
||||||
|
useAlert(
|
||||||
|
this.$t(`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.${errorKey}`)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = error.response?.data?.message;
|
||||||
|
if (message) {
|
||||||
|
useAlert(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
useAlert(this.$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.FAILURE'));
|
||||||
|
},
|
||||||
|
async clearDeletionMark() {
|
||||||
|
try {
|
||||||
|
// Use the enterprise API to toggle deletion with undelete action
|
||||||
|
await this.$store.dispatch('accounts/toggleDeletion', {
|
||||||
|
action_type: 'undelete',
|
||||||
|
});
|
||||||
|
// Refresh account data
|
||||||
|
await this.$store.dispatch('accounts/get');
|
||||||
|
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
|
||||||
|
} catch (error) {
|
||||||
|
useAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col max-w-2xl mx-auto w-full">
|
<div class="flex flex-col w-full">
|
||||||
<BaseSettingsHeader :title="$t('GENERAL_SETTINGS.TITLE')" />
|
<BaseSettingsHeader :title="$t('GENERAL_SETTINGS.TITLE')">
|
||||||
|
<template #actions>
|
||||||
|
<V4Button blue :loading="isUpdating" @click="updateAccount">
|
||||||
|
{{ $t('GENERAL_SETTINGS.SUBMIT') }}
|
||||||
|
</V4Button>
|
||||||
|
</template>
|
||||||
|
</BaseSettingsHeader>
|
||||||
<div class="flex-grow flex-shrink min-w-0 mt-3 overflow-auto">
|
<div class="flex-grow flex-shrink min-w-0 mt-3 overflow-auto">
|
||||||
<SectionLayout
|
<form v-if="!uiFlags.isFetchingItem" @submit.prevent="updateAccount">
|
||||||
:title="$t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.TITLE')"
|
<div
|
||||||
:description="$t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE')"
|
class="flex flex-row border-b border-slate-25 dark:border-slate-800"
|
||||||
>
|
|
||||||
<form
|
|
||||||
v-if="!uiFlags.isFetchingItem"
|
|
||||||
class="grid gap-4"
|
|
||||||
@submit.prevent="updateAccount"
|
|
||||||
>
|
>
|
||||||
<WithLabel
|
<div
|
||||||
:has-error="v$.name.$error"
|
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
||||||
:label="$t('GENERAL_SETTINGS.FORM.NAME.LABEL')"
|
|
||||||
:error-message="$t('GENERAL_SETTINGS.FORM.NAME.ERROR')"
|
|
||||||
>
|
>
|
||||||
<NextInput
|
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
||||||
v-model="name"
|
{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.TITLE') }}
|
||||||
type="text"
|
</h4>
|
||||||
class="w-full"
|
<p>{{ $t('GENERAL_SETTINGS.FORM.GENERAL_SECTION.NOTE') }}</p>
|
||||||
:placeholder="$t('GENERAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
</div>
|
||||||
@blur="v$.name.$touch"
|
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
||||||
/>
|
<label :class="{ error: v$.name.$error }">
|
||||||
</WithLabel>
|
{{ $t('GENERAL_SETTINGS.FORM.NAME.LABEL') }}
|
||||||
<WithLabel
|
<input
|
||||||
:has-error="v$.locale.$error"
|
v-model="name"
|
||||||
:label="$t('GENERAL_SETTINGS.FORM.LANGUAGE.LABEL')"
|
type="text"
|
||||||
:error-message="$t('GENERAL_SETTINGS.FORM.LANGUAGE.ERROR')"
|
:placeholder="$t('GENERAL_SETTINGS.FORM.NAME.PLACEHOLDER')"
|
||||||
>
|
@blur="v$.name.$touch"
|
||||||
<select v-model="locale" class="!mb-0 text-sm">
|
/>
|
||||||
<option
|
<span v-if="v$.name.$error" class="message">
|
||||||
v-for="lang in languagesSortedByCode"
|
{{ $t('GENERAL_SETTINGS.FORM.NAME.ERROR') }}
|
||||||
:key="lang.iso_639_1_code"
|
</span>
|
||||||
:value="lang.iso_639_1_code"
|
</label>
|
||||||
>
|
<label :class="{ error: v$.locale.$error }">
|
||||||
{{ lang.name }}
|
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.LABEL') }}
|
||||||
</option>
|
<select v-model="locale">
|
||||||
</select>
|
<option
|
||||||
</WithLabel>
|
v-for="lang in languagesSortedByCode"
|
||||||
<WithLabel
|
:key="lang.iso_639_1_code"
|
||||||
v-if="featureCustomReplyDomainEnabled"
|
:value="lang.iso_639_1_code"
|
||||||
:label="$t('GENERAL_SETTINGS.FORM.DOMAIN.LABEL')"
|
>
|
||||||
>
|
{{ lang.name }}
|
||||||
<NextInput
|
</option>
|
||||||
v-model="domain"
|
</select>
|
||||||
type="text"
|
<span v-if="v$.locale.$error" class="message">
|
||||||
class="w-full"
|
{{ $t('GENERAL_SETTINGS.FORM.LANGUAGE.ERROR') }}
|
||||||
:placeholder="$t('GENERAL_SETTINGS.FORM.DOMAIN.PLACEHOLDER')"
|
</span>
|
||||||
/>
|
</label>
|
||||||
<template #help>
|
<label v-if="featureInboundEmailEnabled">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.FEATURES.INBOUND_EMAIL_ENABLED') }}
|
||||||
|
</label>
|
||||||
|
<label v-if="featureCustomReplyDomainEnabled">
|
||||||
{{
|
{{
|
||||||
featureInboundEmailEnabled &&
|
|
||||||
$t('GENERAL_SETTINGS.FORM.FEATURES.INBOUND_EMAIL_ENABLED')
|
|
||||||
}}
|
|
||||||
|
|
||||||
{{
|
|
||||||
featureCustomReplyDomainEnabled &&
|
|
||||||
$t('GENERAL_SETTINGS.FORM.FEATURES.CUSTOM_EMAIL_DOMAIN_ENABLED')
|
$t('GENERAL_SETTINGS.FORM.FEATURES.CUSTOM_EMAIL_DOMAIN_ENABLED')
|
||||||
}}
|
}}
|
||||||
</template>
|
</label>
|
||||||
</WithLabel>
|
<label v-if="featureCustomReplyDomainEnabled">
|
||||||
<WithLabel
|
{{ $t('GENERAL_SETTINGS.FORM.DOMAIN.LABEL') }}
|
||||||
v-if="featureCustomReplyEmailEnabled"
|
<input
|
||||||
:label="$t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.LABEL')"
|
v-model="domain"
|
||||||
>
|
type="text"
|
||||||
<NextInput
|
:placeholder="$t('GENERAL_SETTINGS.FORM.DOMAIN.PLACEHOLDER')"
|
||||||
v-model="supportEmail"
|
/>
|
||||||
type="text"
|
</label>
|
||||||
class="w-full"
|
<label v-if="featureCustomReplyEmailEnabled">
|
||||||
:placeholder="
|
{{ $t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.LABEL') }}
|
||||||
$t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.PLACEHOLDER')
|
<input
|
||||||
"
|
v-model="supportEmail"
|
||||||
/>
|
type="text"
|
||||||
</WithLabel>
|
:placeholder="
|
||||||
<div>
|
$t('GENERAL_SETTINGS.FORM.SUPPORT_EMAIL.PLACEHOLDER')
|
||||||
<NextButton blue :is-loading="isUpdating" type="submit">
|
"
|
||||||
{{ $t('GENERAL_SETTINGS.SUBMIT') }}
|
/>
|
||||||
</NextButton>
|
</label>
|
||||||
|
<label
|
||||||
|
v-if="showAutoResolutionConfig"
|
||||||
|
:class="{ error: v$.autoResolveDuration.$error }"
|
||||||
|
>
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.LABEL') }}
|
||||||
|
<input
|
||||||
|
v-model="autoResolveDuration"
|
||||||
|
type="number"
|
||||||
|
:placeholder="
|
||||||
|
$t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
@blur="v$.autoResolveDuration.$touch"
|
||||||
|
/>
|
||||||
|
<span v-if="v$.autoResolveDuration.$error" class="message">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</SectionLayout>
|
</form>
|
||||||
|
|
||||||
<woot-loading-state v-if="uiFlags.isFetchingItem" />
|
<woot-loading-state v-if="uiFlags.isFetchingItem" />
|
||||||
</div>
|
</div>
|
||||||
<AutoResolve v-if="showAutoResolutionConfig" />
|
|
||||||
<AccountId />
|
<div class="flex flex-row">
|
||||||
|
<div class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0">
|
||||||
|
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE') }}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{{ $t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.NOTE') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
||||||
|
<woot-code :script="getAccountId" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
|
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
|
||||||
<AccountDelete />
|
<div
|
||||||
|
class="flex flex-row pt-4 mt-2 border-t border-slate-25 dark:border-slate-800 text-black-900 dark:text-slate-300"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex-grow-0 flex-shrink-0 flex-[25%] min-w-0 py-4 pr-6 pl-0"
|
||||||
|
>
|
||||||
|
<h4 class="text-lg font-medium text-black-900 dark:text-slate-200">
|
||||||
|
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.TITLE') }}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{{ $t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.NOTE') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 flex-grow-0 flex-shrink-0 flex-[50%]">
|
||||||
|
<div v-if="isMarkedForDeletion">
|
||||||
|
<div
|
||||||
|
class="p-4 flex-grow-0 flex-shrink-0 flex-[50%] bg-red-50 dark:bg-red-900 rounded"
|
||||||
|
>
|
||||||
|
<p class="mb-4">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.MESSAGE_${markedForDeletionReason === 'manual_deletion' ? 'MANUAL' : 'INACTIVITY'}`,
|
||||||
|
{
|
||||||
|
deletionDate: formattedDeletionDate,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<NextButton
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.CLEAR_BUTTON'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
color="ruby"
|
||||||
|
:is-loading="uiFlags.isUpdating"
|
||||||
|
@click="clearDeletionMark"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isMarkedForDeletion">
|
||||||
|
<NextButton
|
||||||
|
:label="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.BUTTON_TEXT')"
|
||||||
|
color="ruby"
|
||||||
|
@click="openDeletePopup()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<WootConfirmDeleteModal
|
||||||
|
v-if="showDeletePopup"
|
||||||
|
v-model:show="showDeletePopup"
|
||||||
|
:title="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.TITLE')"
|
||||||
|
:message="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.MESSAGE')"
|
||||||
|
:confirm-text="
|
||||||
|
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.BUTTON_TEXT')
|
||||||
|
"
|
||||||
|
:reject-text="
|
||||||
|
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.DISMISS')
|
||||||
|
"
|
||||||
|
:confirm-value="name"
|
||||||
|
:confirm-place-holder-text="confirmPlaceHolderText"
|
||||||
|
@on-confirm="markAccountForDeletion"
|
||||||
|
@on-close="closeDeletePopup"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 text-sm text-center">
|
||||||
|
<div>{{ `v${globalConfig.appVersion}` }}</div>
|
||||||
|
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
||||||
|
{{
|
||||||
|
$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
|
||||||
|
latestChatwootVersion: latestChatwootVersion,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="build-id">
|
||||||
|
<div>{{ `Build ${globalConfig.gitSha}` }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BuildInfo />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
-149
@@ -1,149 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useToggle } from '@vueuse/core';
|
|
||||||
import { useAlert } from 'dashboard/composables';
|
|
||||||
import WootConfirmDeleteModal from 'dashboard/components/widgets/modal/ConfirmDeleteModal.vue';
|
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
import SectionLayout from './SectionLayout.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const store = useStore();
|
|
||||||
const uiFlags = useMapGetter('accounts/getUIFlags');
|
|
||||||
const { currentAccount } = useAccount();
|
|
||||||
const [showDeletePopup, toggleDeletePopup] = useToggle();
|
|
||||||
|
|
||||||
const confirmPlaceHolderText = computed(() => {
|
|
||||||
return `${t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.PLACE_HOLDER', {
|
|
||||||
accountName: currentAccount.value.name,
|
|
||||||
})}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isMarkedForDeletion = computed(() => {
|
|
||||||
const { custom_attributes = {} } = currentAccount.value;
|
|
||||||
return !!custom_attributes.marked_for_deletion_at;
|
|
||||||
});
|
|
||||||
|
|
||||||
const markedForDeletionDate = computed(() => {
|
|
||||||
const { custom_attributes = {} } = currentAccount.value;
|
|
||||||
if (!custom_attributes.marked_for_deletion_at) return null;
|
|
||||||
return new Date(custom_attributes.marked_for_deletion_at);
|
|
||||||
});
|
|
||||||
|
|
||||||
const markedForDeletionReason = computed(() => {
|
|
||||||
const { custom_attributes = {} } = currentAccount.value;
|
|
||||||
return custom_attributes.marked_for_deletion_reason || 'manual_deletion';
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedDeletionDate = computed(() => {
|
|
||||||
if (!markedForDeletionDate.value) return '';
|
|
||||||
return markedForDeletionDate.value.toLocaleString();
|
|
||||||
});
|
|
||||||
|
|
||||||
const markedForDeletionMessage = computed(() => {
|
|
||||||
const params = { deletionDate: formattedDeletionDate.value };
|
|
||||||
|
|
||||||
if (markedForDeletionReason.value === 'manual_deletion') {
|
|
||||||
return t(
|
|
||||||
`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.MESSAGE_MANUAL`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return t(
|
|
||||||
`GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.MESSAGE_INACTIVITY`,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleDeletionError(error) {
|
|
||||||
const message = error.response?.data?.message;
|
|
||||||
if (message) {
|
|
||||||
useAlert(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
useAlert(t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.FAILURE'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function markAccountForDeletion() {
|
|
||||||
toggleDeletePopup(false);
|
|
||||||
try {
|
|
||||||
// Use the enterprise API to toggle deletion with delete action
|
|
||||||
await store.dispatch('accounts/toggleDeletion', {
|
|
||||||
action_type: 'delete',
|
|
||||||
});
|
|
||||||
// Refresh account data
|
|
||||||
await store.dispatch('accounts/get');
|
|
||||||
useAlert(t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
// Handle error message
|
|
||||||
handleDeletionError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clearDeletionMark() {
|
|
||||||
try {
|
|
||||||
// Use the enterprise API to toggle deletion with undelete action
|
|
||||||
await store.dispatch('accounts/toggleDeletion', {
|
|
||||||
action_type: 'undelete',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh account data
|
|
||||||
await store.dispatch('accounts/get');
|
|
||||||
useAlert(t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
useAlert(t('GENERAL_SETTINGS.UPDATE.ERROR'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SectionLayout
|
|
||||||
:title="t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.TITLE')"
|
|
||||||
:description="t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.NOTE')"
|
|
||||||
with-border
|
|
||||||
>
|
|
||||||
<div v-if="isMarkedForDeletion">
|
|
||||||
<div
|
|
||||||
class="p-4 flex-grow-0 flex-shrink-0 flex-[50%] bg-red-50 dark:bg-red-900 rounded"
|
|
||||||
>
|
|
||||||
<p class="mb-4">
|
|
||||||
{{ markedForDeletionMessage }}
|
|
||||||
</p>
|
|
||||||
<NextButton
|
|
||||||
:label="
|
|
||||||
$t(
|
|
||||||
'GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.SCHEDULED_DELETION.CLEAR_BUTTON'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
color="ruby"
|
|
||||||
:is-loading="uiFlags.isUpdating"
|
|
||||||
@click="clearDeletionMark"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!isMarkedForDeletion">
|
|
||||||
<NextButton
|
|
||||||
:label="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.BUTTON_TEXT')"
|
|
||||||
color="ruby"
|
|
||||||
@click="toggleDeletePopup(true)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</SectionLayout>
|
|
||||||
<WootConfirmDeleteModal
|
|
||||||
v-if="showDeletePopup"
|
|
||||||
v-model:show="showDeletePopup"
|
|
||||||
:title="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.TITLE')"
|
|
||||||
:message="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.MESSAGE')"
|
|
||||||
:confirm-text="
|
|
||||||
$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.BUTTON_TEXT')
|
|
||||||
"
|
|
||||||
:reject-text="$t('GENERAL_SETTINGS.ACCOUNT_DELETE_SECTION.CONFIRM.DISMISS')"
|
|
||||||
:confirm-value="currentAccount.name"
|
|
||||||
:confirm-place-holder-text="confirmPlaceHolderText"
|
|
||||||
@on-confirm="markAccountForDeletion"
|
|
||||||
@on-close="toggleDeletePopup(false)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import SectionLayout from './SectionLayout.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const { currentAccount } = useAccount();
|
|
||||||
|
|
||||||
const getAccountId = computed(() => currentAccount.value.id.toString());
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SectionLayout
|
|
||||||
:title="t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.TITLE')"
|
|
||||||
:description="t('GENERAL_SETTINGS.FORM.ACCOUNT_ID.NOTE')"
|
|
||||||
with-border
|
|
||||||
>
|
|
||||||
<woot-code :script="getAccountId" />
|
|
||||||
</SectionLayout>
|
|
||||||
</template>
|
|
||||||
-119
@@ -1,119 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
|
||||||
import { useAlert } from 'dashboard/composables';
|
|
||||||
import SectionLayout from './SectionLayout.vue';
|
|
||||||
import WithLabel from 'v3/components/Form/WithLabel.vue';
|
|
||||||
import DurationInput from 'next/input/DurationInput.vue';
|
|
||||||
import TextArea from 'next/textarea/TextArea.vue';
|
|
||||||
import Switch from 'next/switch/Switch.vue';
|
|
||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const duration = ref(0);
|
|
||||||
const message = ref('');
|
|
||||||
const isEnabled = ref(false);
|
|
||||||
|
|
||||||
const { currentAccount, updateAccount } = useAccount();
|
|
||||||
|
|
||||||
watch(
|
|
||||||
currentAccount,
|
|
||||||
() => {
|
|
||||||
const { auto_resolve_after, auto_resolve_message } =
|
|
||||||
currentAccount.value?.settings || {};
|
|
||||||
|
|
||||||
duration.value = auto_resolve_after;
|
|
||||||
message.value = auto_resolve_message;
|
|
||||||
|
|
||||||
if (duration.value) {
|
|
||||||
isEnabled.value = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateAccountSettings = async settings => {
|
|
||||||
try {
|
|
||||||
await updateAccount(settings);
|
|
||||||
useAlert(t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.API.SUCCESS'));
|
|
||||||
} catch (error) {
|
|
||||||
useAlert(t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.API.ERROR'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
return updateAccountSettings({
|
|
||||||
auto_resolve_after: duration.value,
|
|
||||||
auto_resolve_message: message.value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDisable = async () => {
|
|
||||||
duration.value = null;
|
|
||||||
message.value = '';
|
|
||||||
|
|
||||||
return updateAccountSettings({
|
|
||||||
auto_resolve_after: null,
|
|
||||||
auto_resolve_message: '',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleAutoResolve = async () => {
|
|
||||||
if (!isEnabled.value) handleDisable();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SectionLayout
|
|
||||||
:title="t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE.TITLE')"
|
|
||||||
:description="t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE.NOTE')"
|
|
||||||
with-border
|
|
||||||
>
|
|
||||||
<template #headerActions>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<Switch v-model="isEnabled" @change="toggleAutoResolve" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<form class="grid gap-4" @submit.prevent="handleSubmit">
|
|
||||||
<WithLabel
|
|
||||||
:label="t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.LABEL')"
|
|
||||||
:help-message="t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.HELP')"
|
|
||||||
>
|
|
||||||
<div class="gap-2 w-full grid grid-cols-[3fr_1fr]">
|
|
||||||
<!-- allow 10 mins to 999 days -->
|
|
||||||
<DurationInput
|
|
||||||
v-model="duration"
|
|
||||||
min="10"
|
|
||||||
max="1439856"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</WithLabel>
|
|
||||||
<WithLabel
|
|
||||||
:label="t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.MESSAGE_LABEL')"
|
|
||||||
:help-message="
|
|
||||||
t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.MESSAGE_HELP')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<TextArea
|
|
||||||
v-model="message"
|
|
||||||
class="w-full"
|
|
||||||
:placeholder="
|
|
||||||
t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.MESSAGE_PLACEHOLDER')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</WithLabel>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<NextButton
|
|
||||||
blue
|
|
||||||
type="submit"
|
|
||||||
:label="
|
|
||||||
t('GENERAL_SETTINGS.FORM.AUTO_RESOLVE_DURATION.UPDATE_BUTTON')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</SectionLayout>
|
|
||||||
</template>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
|
||||||
import { useMapGetter } from 'dashboard/composables/store';
|
|
||||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import semver from 'semver';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const { currentAccount } = useAccount();
|
|
||||||
|
|
||||||
const latestChatwootVersion = computed(() => {
|
|
||||||
return currentAccount.value.latest_chatwoot_version;
|
|
||||||
});
|
|
||||||
|
|
||||||
const globalConfig = useMapGetter('globalConfig/get');
|
|
||||||
|
|
||||||
const hasAnUpdateAvailable = computed(() => {
|
|
||||||
if (!semver.valid(latestChatwootVersion.value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return semver.lt(globalConfig.value.appVersion, latestChatwootVersion.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const gitSha = computed(() => {
|
|
||||||
return globalConfig.value.gitSha.substring(0, 7);
|
|
||||||
});
|
|
||||||
|
|
||||||
const copyGitSha = () => {
|
|
||||||
copyTextToClipboard(globalConfig.value.gitSha);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="p-4 text-sm text-center">
|
|
||||||
<div v-if="hasAnUpdateAvailable && globalConfig.displayManifest">
|
|
||||||
{{
|
|
||||||
t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
|
|
||||||
latestChatwootVersion: latestChatwootVersion,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="divide-x divide-n-slate-9">
|
|
||||||
<span class="px-2">{{ `v${globalConfig.appVersion}` }}</span>
|
|
||||||
<span
|
|
||||||
v-tooltip="t('COMPONENTS.CODE.BUTTON_TEXT')"
|
|
||||||
class="px-2 build-id cursor-pointer"
|
|
||||||
@click="copyGitSha"
|
|
||||||
>
|
|
||||||
{{ `Build ${gitSha}` }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
withBorder: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<section
|
|
||||||
class="grid grid-cols-1 py-8 gap-10"
|
|
||||||
:class="{ 'border-t border-n-weak': withBorder }"
|
|
||||||
>
|
|
||||||
<header class="grid grid-cols-4">
|
|
||||||
<div class="col-span-3">
|
|
||||||
<h4 class="text-lg font-medium text-n-slate-12">
|
|
||||||
<slot name="title">{{ title }}</slot>
|
|
||||||
</h4>
|
|
||||||
<p class="text-n-slate-11">
|
|
||||||
<slot name="description">{{ description }}</slot>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-1">
|
|
||||||
<slot name="headerActions" />
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="text-n-slate-12">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
@@ -14,13 +14,8 @@ import SettingsLayout from '../SettingsLayout.vue';
|
|||||||
import ButtonV4 from 'next/button/Button.vue';
|
import ButtonV4 from 'next/button/Button.vue';
|
||||||
|
|
||||||
const { currentAccount } = useAccount();
|
const { currentAccount } = useAccount();
|
||||||
const {
|
const { captainEnabled, captainLimits, documentLimits, responseLimits } =
|
||||||
captainEnabled,
|
useCaptain();
|
||||||
captainLimits,
|
|
||||||
documentLimits,
|
|
||||||
responseLimits,
|
|
||||||
fetchLimits,
|
|
||||||
} = useCaptain();
|
|
||||||
|
|
||||||
const uiFlags = useMapGetter('accounts/getUIFlags');
|
const uiFlags = useMapGetter('accounts/getUIFlags');
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
@@ -62,7 +57,6 @@ const hasABillingPlan = computed(() => {
|
|||||||
const fetchAccountDetails = async () => {
|
const fetchAccountDetails = async () => {
|
||||||
if (!hasABillingPlan.value) {
|
if (!hasABillingPlan.value) {
|
||||||
store.dispatch('accounts/subscription');
|
store.dispatch('accounts/subscription');
|
||||||
fetchLimits();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, computed, defineExpose, defineProps } from 'vue';
|
import { computed, defineExpose } from 'vue';
|
||||||
import { useStore } from 'dashboard/composables/store';
|
|
||||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||||
@@ -11,20 +10,14 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
bypassUpgradePage: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { accountId, currentAccount } = useAccount();
|
const { accountId, currentAccount } = useAccount();
|
||||||
const { isAdmin } = useAdmin();
|
const { isAdmin } = useAdmin();
|
||||||
|
|
||||||
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
|
const isOnChatwootCloud = useMapGetter('globalConfig/isOnChatwootCloud');
|
||||||
|
const accountUIFlags = useMapGetter('accounts/getUIFlags');
|
||||||
|
|
||||||
const testLimit = ({ allowed, consumed }) => {
|
const testLimit = ({ allowed, consumed }) => {
|
||||||
return consumed > allowed;
|
return consumed > allowed;
|
||||||
@@ -41,6 +34,14 @@ const isTrialAccount = computed(() => {
|
|||||||
return diffDays <= 15;
|
return diffDays <= 15;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bypassUpgradePage = computed(() => {
|
||||||
|
return [
|
||||||
|
'billing_settings_index',
|
||||||
|
'settings_inbox_list',
|
||||||
|
'agent_list',
|
||||||
|
].includes(route.name);
|
||||||
|
});
|
||||||
|
|
||||||
const limitExceededMessage = computed(() => {
|
const limitExceededMessage = computed(() => {
|
||||||
const account = currentAccount.value;
|
const account = currentAccount.value;
|
||||||
if (!account?.limits) return '';
|
if (!account?.limits) return '';
|
||||||
@@ -80,17 +81,14 @@ const isLimitExceeded = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const shouldShowUpgradePage = computed(() => {
|
const shouldShowUpgradePage = computed(() => {
|
||||||
|
// Hide upgrade page while fetching limits
|
||||||
|
if (accountUIFlags.isFetchingLimits) return false;
|
||||||
// Skip upgrade page in Billing, Inbox, and Agent pages
|
// Skip upgrade page in Billing, Inbox, and Agent pages
|
||||||
if (props.bypassUpgradePage) return false;
|
if (bypassUpgradePage.value) return false;
|
||||||
if (!isOnChatwootCloud.value) return false;
|
if (!isOnChatwootCloud.value) return false;
|
||||||
if (isTrialAccount.value) return false;
|
if (isTrialAccount.value) return false;
|
||||||
return isLimitExceeded.value;
|
return isLimitExceeded.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchLimits = () => {
|
|
||||||
store.dispatch('accounts/limits');
|
|
||||||
};
|
|
||||||
|
|
||||||
const routeToBilling = () => {
|
const routeToBilling = () => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'billing_settings_index',
|
name: 'billing_settings_index',
|
||||||
@@ -98,8 +96,6 @@ const routeToBilling = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => fetchLimits());
|
|
||||||
|
|
||||||
defineExpose({ shouldShowUpgradePage });
|
defineExpose({ shouldShowUpgradePage });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const state = {
|
|||||||
isFetchingItem: false,
|
isFetchingItem: false,
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
isCheckoutInProcess: false,
|
isCheckoutInProcess: false,
|
||||||
|
isFetchingLimits: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,8 +67,7 @@ export const actions = {
|
|||||||
update: async ({ commit }, updateObj) => {
|
update: async ({ commit }, updateObj) => {
|
||||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
|
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true });
|
||||||
try {
|
try {
|
||||||
const response = await AccountAPI.update('', updateObj);
|
await AccountAPI.update('', updateObj);
|
||||||
commit(types.default.EDIT_ACCOUNT, response.data);
|
|
||||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
|
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
|
commit(types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false });
|
||||||
@@ -134,11 +134,14 @@ export const actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
limits: async ({ commit }) => {
|
limits: async ({ commit }) => {
|
||||||
|
commit(types.default.SET_ACCOUNT_UI_FLAG, { isFetchingLimits: true });
|
||||||
try {
|
try {
|
||||||
const response = await EnterpriseAccountAPI.getLimits();
|
const response = await EnterpriseAccountAPI.getLimits();
|
||||||
commit(types.default.SET_ACCOUNT_LIMITS, response.data);
|
commit(types.default.SET_ACCOUNT_LIMITS, response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// silent error
|
// silent error
|
||||||
|
} finally {
|
||||||
|
commit(types.default.SET_ACCOUNT_UI_FLAG, { isFetchingLimits: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,10 @@ describe('#actions', () => {
|
|||||||
|
|
||||||
describe('#update', () => {
|
describe('#update', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
axios.patch.mockResolvedValue({
|
axios.patch.mockResolvedValue();
|
||||||
data: { id: 1, name: 'John' },
|
|
||||||
});
|
|
||||||
await actions.update({ commit, getters }, accountData);
|
await actions.update({ commit, getters }, accountData);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true }],
|
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: true }],
|
||||||
[types.default.EDIT_ACCOUNT, { id: 1, name: 'John' }],
|
|
||||||
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false }],
|
[types.default.SET_ACCOUNT_UI_FLAG, { isUpdating: false }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,18 +23,12 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a Rails Turbo app
|
if (window.Turbo) {
|
||||||
document.addEventListener('turbo:before-render', event => {
|
// if this is a Rails Turbo app
|
||||||
// when morphing the page, this typically happens on reload like events
|
document.addEventListener('turbo:before-render', event =>
|
||||||
// say you update a "Customer" on a form and it reloads the page
|
restoreWidgetInDOM(event.detail.newBody)
|
||||||
// We have already added data-turbo-permananent to true. This
|
);
|
||||||
// will ensure that the widget it preserved
|
}
|
||||||
// Read more about morphing here: https://turbo.hotwired.dev/handbook/page_refreshes#morphing
|
|
||||||
// and peristing elements here: https://turbo.hotwired.dev/handbook/building#persisting-elements-across-page-loads
|
|
||||||
if (event.detail.renderMethod === 'morph') return;
|
|
||||||
|
|
||||||
restoreWidgetInDOM(event.detail.newBody);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.Turbolinks) {
|
if (window.Turbolinks) {
|
||||||
document.addEventListener('turbolinks:before-render', event => {
|
document.addEventListener('turbolinks:before-render', event => {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export const loadCSS = () => {
|
|||||||
const css = document.createElement('style');
|
const css = document.createElement('style');
|
||||||
css.innerHTML = `${SDK_CSS}`;
|
css.innerHTML = `${SDK_CSS}`;
|
||||||
css.id = 'cw-widget-styles';
|
css.id = 'cw-widget-styles';
|
||||||
css.dataset.turboPermanent = true;
|
|
||||||
document.body.appendChild(css);
|
document.body.appendChild(css);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ export const IFrameHelper = {
|
|||||||
|
|
||||||
addClasses(widgetHolder, holderClassName);
|
addClasses(widgetHolder, holderClassName);
|
||||||
widgetHolder.id = 'cw-widget-holder';
|
widgetHolder.id = 'cw-widget-holder';
|
||||||
widgetHolder.dataset.turboPermanent = true;
|
|
||||||
widgetHolder.appendChild(iframe);
|
widgetHolder.appendChild(iframe);
|
||||||
body.appendChild(widgetHolder);
|
body.appendChild(widgetHolder);
|
||||||
IFrameHelper.initPostMessageCommunication();
|
IFrameHelper.initPostMessageCommunication();
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export const createBubbleHolder = hideMessageBubble => {
|
|||||||
}
|
}
|
||||||
addClasses(bubbleHolder, 'woot--bubble-holder');
|
addClasses(bubbleHolder, 'woot--bubble-holder');
|
||||||
bubbleHolder.id = 'cw-bubble-holder';
|
bubbleHolder.id = 'cw-bubble-holder';
|
||||||
bubbleHolder.dataset.turboPermanent = true;
|
|
||||||
body.appendChild(bubbleHolder);
|
body.appendChild(bubbleHolder);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
<script setup>
|
<script>
|
||||||
defineProps({
|
export default {
|
||||||
label: { type: String, default: '' },
|
props: {
|
||||||
name: { type: String, required: true },
|
label: {
|
||||||
icon: { type: String, default: '' },
|
type: String,
|
||||||
hasError: { type: Boolean, default: false },
|
default: '',
|
||||||
helpMessage: { type: String, default: '' },
|
},
|
||||||
errorMessage: { type: String, default: '' },
|
name: {
|
||||||
});
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -14,8 +30,8 @@ defineProps({
|
|||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="name"
|
:for="name"
|
||||||
class="flex justify-between text-sm font-medium leading-6 text-n-slate-12"
|
class="flex justify-between text-sm font-medium leading-6 text-slate-900 dark:text-white"
|
||||||
:class="{ 'text-n-ruby-12': hasError }"
|
:class="{ 'text-red-500': hasError }"
|
||||||
>
|
>
|
||||||
<slot name="label">
|
<slot name="label">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -28,24 +44,16 @@ defineProps({
|
|||||||
v-if="icon"
|
v-if="icon"
|
||||||
size="16"
|
size="16"
|
||||||
:icon="icon"
|
:icon="icon"
|
||||||
class="absolute left-2 transform text-n-slate-9 w-5 h-5"
|
class="absolute left-2 transform text-slate-400 dark:text-slate-600 w-5 h-5"
|
||||||
/>
|
/>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<span
|
||||||
v-if="errorMessage && hasError"
|
v-if="errorMessage && hasError"
|
||||||
class="text-xs mt-2 ml-px text-n-ruby-9 leading-tight"
|
class="text-xs text-n-ruby-9 dark:text-n-ruby-9 leading-2"
|
||||||
>
|
>
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</div>
|
</span>
|
||||||
<div
|
|
||||||
v-else-if="helpMessage || $slots.help"
|
|
||||||
class="text-xs mt-2 ml-px text-n-slate-10 leading-tight"
|
|
||||||
>
|
|
||||||
<slot name="help">
|
|
||||||
{{ helpMessage }}
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ class Account::ConversationsResolutionSchedulerJob < ApplicationJob
|
|||||||
queue_as :scheduled_jobs
|
queue_as :scheduled_jobs
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
Account.with_auto_resolve.find_each(batch_size: 100) do |account|
|
Account.where.not(auto_resolve_duration: nil).all.find_each(batch_size: 100) do |account|
|
||||||
Conversations::ResolutionJob.perform_later(account: account)
|
Conversations::ResolutionJob.perform_later(account: account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ class Conversations::ResolutionJob < ApplicationJob
|
|||||||
|
|
||||||
def perform(account:)
|
def perform(account:)
|
||||||
# limiting the number of conversations to be resolved to avoid any performance issues
|
# limiting the number of conversations to be resolved to avoid any performance issues
|
||||||
resolvable_conversations = account.conversations.resolvable(account.auto_resolve_after).limit(Limits::BULK_ACTIONS_LIMIT)
|
resolvable_conversations = account.conversations.resolvable(account.auto_resolve_duration).limit(Limits::BULK_ACTIONS_LIMIT)
|
||||||
resolvable_conversations.each do |conversation|
|
resolvable_conversations.each(&:toggle_status)
|
||||||
# send message from bot that conversation has been resolved
|
|
||||||
# do this is account.auto_resolve_message is set
|
|
||||||
::MessageTemplates::Template::AutoResolve.new(conversation: conversation).perform if account.auto_resolve_message.present?
|
|
||||||
conversation.toggle_status
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# - are older than 30 days
|
# - are older than 30 days
|
||||||
|
|
||||||
class Internal::ProcessStaleContactsJob < ApplicationJob
|
class Internal::ProcessStaleContactsJob < ApplicationJob
|
||||||
queue_as :housekeeping
|
queue_as :scheduled_jobs
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
return unless ChatwootApp.chatwoot_cloud?
|
return unless ChatwootApp.chatwoot_cloud?
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# - are older than 30 days
|
# - are older than 30 days
|
||||||
|
|
||||||
class Internal::RemoveStaleContactsJob < ApplicationJob
|
class Internal::RemoveStaleContactsJob < ApplicationJob
|
||||||
queue_as :housekeeping
|
queue_as :low
|
||||||
|
|
||||||
def perform(account, batch_size = 1000)
|
def perform(account, batch_size = 1000)
|
||||||
Internal::RemoveStaleContactsService.new(account: account).perform(batch_size)
|
Internal::RemoveStaleContactsService.new(account: account).perform(batch_size)
|
||||||
|
|||||||
+1
-19
@@ -11,7 +11,6 @@
|
|||||||
# limits :jsonb
|
# limits :jsonb
|
||||||
# locale :integer default("en")
|
# locale :integer default("en")
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# settings :jsonb
|
|
||||||
# status :integer default("active")
|
# status :integer default("active")
|
||||||
# support_email :string(100)
|
# support_email :string(100)
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
@@ -29,28 +28,13 @@ class Account < ApplicationRecord
|
|||||||
include Featurable
|
include Featurable
|
||||||
include CacheKeys
|
include CacheKeys
|
||||||
|
|
||||||
SETTINGS_PARAMS_SCHEMA = {
|
|
||||||
'type': 'object',
|
|
||||||
'properties':
|
|
||||||
{
|
|
||||||
'auto_resolve_after': { 'type': %w[integer null], 'minimum': 10, 'maximum': 1_439_856 },
|
|
||||||
'auto_resolve_message': { 'type': %w[string null] }
|
|
||||||
},
|
|
||||||
'required': [],
|
|
||||||
'additionalProperties': false
|
|
||||||
}.to_json.freeze
|
|
||||||
|
|
||||||
DEFAULT_QUERY_SETTING = {
|
DEFAULT_QUERY_SETTING = {
|
||||||
flag_query_mode: :bit_operator,
|
flag_query_mode: :bit_operator,
|
||||||
check_for_column: false
|
check_for_column: false
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
validates :auto_resolve_duration, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 999, allow_nil: true }
|
||||||
validates :domain, length: { maximum: 100 }
|
validates :domain, length: { maximum: 100 }
|
||||||
validates_with JsonSchemaValidator,
|
|
||||||
schema: SETTINGS_PARAMS_SCHEMA,
|
|
||||||
attribute_resolver: ->(record) { record.settings }
|
|
||||||
|
|
||||||
store_accessor :settings, :auto_resolve_after, :auto_resolve_message
|
|
||||||
|
|
||||||
has_many :account_users, dependent: :destroy_async
|
has_many :account_users, dependent: :destroy_async
|
||||||
has_many :agent_bot_inboxes, dependent: :destroy_async
|
has_many :agent_bot_inboxes, dependent: :destroy_async
|
||||||
@@ -99,8 +83,6 @@ class Account < ApplicationRecord
|
|||||||
enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h
|
enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h
|
||||||
enum status: { active: 0, suspended: 1 }
|
enum status: { active: 0, suspended: 1 }
|
||||||
|
|
||||||
scope :with_auto_resolve, -> { where("(settings ->> 'auto_resolve_after')::int IS NOT NULL") }
|
|
||||||
|
|
||||||
before_validation :validate_limit_keys
|
before_validation :validate_limit_keys
|
||||||
after_create_commit :notify_creation
|
after_create_commit :notify_creation
|
||||||
after_destroy :remove_account_sequences
|
after_destroy :remove_account_sequences
|
||||||
|
|||||||
@@ -56,24 +56,13 @@ module ActivityMessageHandler
|
|||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||||
end
|
end
|
||||||
|
|
||||||
def auto_resolve_message_key(minutes)
|
|
||||||
if minutes >= 1440 && (minutes % 1440).zero?
|
|
||||||
{ key: 'auto_resolved_days', count: minutes / 1440 }
|
|
||||||
elsif minutes >= 60 && (minutes % 60).zero?
|
|
||||||
{ key: 'auto_resolved_hours', count: minutes / 60 }
|
|
||||||
else
|
|
||||||
{ key: 'auto_resolved_minutes', count: minutes }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def user_status_change_activity_content(user_name)
|
def user_status_change_activity_content(user_name)
|
||||||
if user_name
|
if user_name
|
||||||
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
||||||
elsif Current.contact.present? && resolved?
|
elsif Current.contact.present? && resolved?
|
||||||
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
||||||
elsif resolved?
|
elsif resolved?
|
||||||
message_data = auto_resolve_message_key(auto_resolve_after || 0)
|
I18n.t('conversations.activity.status.auto_resolved', duration: auto_resolve_duration)
|
||||||
I18n.t("conversations.activity.status.#{message_data[:key]}", count: message_data[:count])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ class JsonSchemaValidator < ActiveModel::Validator
|
|||||||
|
|
||||||
def format_and_append_error(error, record)
|
def format_and_append_error(error, record)
|
||||||
return handle_required(error, record) if error['type'] == 'required'
|
return handle_required(error, record) if error['type'] == 'required'
|
||||||
return handle_minimum(error, record) if error['type'] == 'minimum'
|
|
||||||
return handle_maximum(error, record) if error['type'] == 'maximum'
|
|
||||||
|
|
||||||
type = error['type'] == 'object' ? 'hash' : error['type']
|
type = error['type'] == 'object' ? 'hash' : error['type']
|
||||||
|
|
||||||
@@ -76,16 +74,6 @@ class JsonSchemaValidator < ActiveModel::Validator
|
|||||||
record.errors.add(data, "must be of type #{expected_type}")
|
record.errors.add(data, "must be of type #{expected_type}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_minimum(error, record)
|
|
||||||
data = get_name_from_data_pointer(error)
|
|
||||||
record.errors.add(data, "must be greater than or equal to #{error['schema']['minimum']}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_maximum(error, record)
|
|
||||||
data = get_name_from_data_pointer(error)
|
|
||||||
record.errors.add(data, "must be less than or equal to #{error['schema']['maximum']}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_name_from_data_pointer(error)
|
def get_name_from_data_pointer(error)
|
||||||
data = error['data_pointer']
|
data = error['data_pointer']
|
||||||
|
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ class Conversation < ApplicationRecord
|
|||||||
scope :assigned, -> { where.not(assignee_id: nil) }
|
scope :assigned, -> { where.not(assignee_id: nil) }
|
||||||
scope :assigned_to, ->(agent) { where(assignee_id: agent.id) }
|
scope :assigned_to, ->(agent) { where(assignee_id: agent.id) }
|
||||||
scope :unattended, -> { where(first_reply_created_at: nil).or(where.not(waiting_since: nil)) }
|
scope :unattended, -> { where(first_reply_created_at: nil).or(where.not(waiting_since: nil)) }
|
||||||
scope :resolvable, lambda { |auto_resolve_after|
|
scope :resolvable, lambda { |auto_resolve_duration|
|
||||||
return none if auto_resolve_after.to_i.zero?
|
return none if auto_resolve_duration.to_i.zero?
|
||||||
|
|
||||||
open.where('last_activity_at < ? AND waiting_since IS NULL', Time.now.utc - auto_resolve_after.minutes)
|
open.where('last_activity_at < ? ', Time.now.utc - auto_resolve_duration.days)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :last_user_message_at, lambda {
|
scope :last_user_message_at, lambda {
|
||||||
@@ -112,7 +112,7 @@ class Conversation < ApplicationRecord
|
|||||||
after_create_commit :notify_conversation_creation
|
after_create_commit :notify_conversation_creation
|
||||||
after_create_commit :load_attributes_created_by_db_triggers
|
after_create_commit :load_attributes_created_by_db_triggers
|
||||||
|
|
||||||
delegate :auto_resolve_after, to: :account
|
delegate :auto_resolve_duration, to: :account
|
||||||
|
|
||||||
def can_reply?
|
def can_reply?
|
||||||
Conversations::MessageWindowService.new(self).can_reply?
|
Conversations::MessageWindowService.new(self).can_reply?
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
class MessageTemplates::Template::AutoResolve
|
|
||||||
pattr_initialize [:conversation!]
|
|
||||||
|
|
||||||
def perform
|
|
||||||
return if conversation.account.auto_resolve_message.blank?
|
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
|
||||||
conversation.messages.create!(auto_resolve_message_params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
delegate :contact, :account, to: :conversation
|
|
||||||
delegate :inbox, to: :message
|
|
||||||
|
|
||||||
def auto_resolve_message_params
|
|
||||||
{
|
|
||||||
account_id: @conversation.account_id,
|
|
||||||
inbox_id: @conversation.inbox_id,
|
|
||||||
message_type: :template,
|
|
||||||
content: account.auto_resolve_message
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
json.settings resource.settings
|
json.auto_resolve_duration resource.auto_resolve_duration
|
||||||
json.created_at resource.created_at
|
json.created_at resource.created_at
|
||||||
if resource.custom_attributes.present?
|
if resource.custom_attributes.present?
|
||||||
json.custom_attributes do
|
json.custom_attributes do
|
||||||
|
|||||||
@@ -159,9 +159,7 @@ en:
|
|||||||
open: 'Conversation was reopened by %{user_name}'
|
open: 'Conversation was reopened by %{user_name}'
|
||||||
pending: 'Conversation was marked as pending by %{user_name}'
|
pending: 'Conversation was marked as pending by %{user_name}'
|
||||||
snoozed: 'Conversation was snoozed by %{user_name}'
|
snoozed: 'Conversation was snoozed by %{user_name}'
|
||||||
auto_resolved_days: 'Conversation was marked resolved by system due to %{count} days of inactivity'
|
auto_resolved: 'Conversation was marked resolved by system due to %{duration} days of inactivity'
|
||||||
auto_resolved_hours: 'Conversation was marked resolved by system due to %{count} hours of inactivity'
|
|
||||||
auto_resolved_minutes: 'Conversation was marked resolved by system due to %{count} minutes of inactivity'
|
|
||||||
system_auto_open: System reopened the conversation due to a new incoming message.
|
system_auto_open: System reopened the conversation due to a new incoming message.
|
||||||
priority:
|
priority:
|
||||||
added: '%{user_name} set the priority to %{new_priority}'
|
added: '%{user_name} set the priority to %{new_priority}'
|
||||||
|
|||||||
+1
-1
@@ -38,4 +38,4 @@ remove_stale_redis_keys_job.rb:
|
|||||||
process_stale_contacts_job:
|
process_stale_contacts_job:
|
||||||
cron: '30 04 * * *'
|
cron: '30 04 * * *'
|
||||||
class: 'Internal::ProcessStaleContactsJob'
|
class: 'Internal::ProcessStaleContactsJob'
|
||||||
queue: housekeeping
|
queue: scheduled_jobs
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
- action_mailbox_routing
|
- action_mailbox_routing
|
||||||
- low
|
- low
|
||||||
- scheduled_jobs
|
- scheduled_jobs
|
||||||
- housekeeping
|
|
||||||
- async_database_migration
|
- async_database_migration
|
||||||
- active_storage_analysis
|
- active_storage_analysis
|
||||||
- active_storage_purge
|
- active_storage_purge
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
class AddSettingsColumnToAccount < ActiveRecord::Migration[7.0]
|
|
||||||
def change
|
|
||||||
add_column :accounts, :settings, :jsonb, default: {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
class UpdateAutoResolveToMminutes < ActiveRecord::Migration[7.0]
|
|
||||||
def change
|
|
||||||
Account.where.not(auto_resolve_duration: nil).each do |account|
|
|
||||||
account.auto_resolve_after = account.auto_resolve_duration * 60 * 24
|
|
||||||
account.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
+1
-2
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2025_04_21_085134) do
|
ActiveRecord::Schema[7.0].define(version: 2025_04_16_182131) do
|
||||||
# These extensions should be enabled to support this database
|
# These extensions should be enabled to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@@ -58,7 +58,6 @@ ActiveRecord::Schema[7.0].define(version: 2025_04_21_085134) do
|
|||||||
t.jsonb "custom_attributes", default: {}
|
t.jsonb "custom_attributes", default: {}
|
||||||
t.integer "status", default: 0
|
t.integer "status", default: 0
|
||||||
t.jsonb "internal_attributes", default: {}, null: false
|
t.jsonb "internal_attributes", default: {}, null: false
|
||||||
t.jsonb "settings", default: {}
|
|
||||||
t.index ["status"], name: "index_accounts_on_status"
|
t.index ["status"], name: "index_accounts_on_status"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
## Chatwoot Developer Documentation
|
|
||||||
|
|
||||||
Welcome to the official Chatwoot developer documentation. This guide contains everything you need to know about Chatwoot APIs and build custom flows on top of Chatwoot APIs.
|
|
||||||
|
|
||||||
### 👩💻 Development
|
|
||||||
|
|
||||||
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
|
|
||||||
|
|
||||||
```
|
|
||||||
npm i -g mintlify
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the following command at the root of your documentation (where mint.json is)
|
|
||||||
|
|
||||||
```
|
|
||||||
mintlify dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 😎 Publishing Changes
|
|
||||||
|
|
||||||
Changes will be deployed to production automatically after pushing to the default branch.
|
|
||||||
|
|
||||||
You can also preview changes using PRs, which generates a preview link of the docs.
|
|
||||||
|
|
||||||
#### Troubleshooting
|
|
||||||
|
|
||||||
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
|
|
||||||
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Chatwoot Developer Documentations",
|
|
||||||
"description": "Official developer documentation for Chatwoot - the open-source customer support platform. Learn about our APIs, integrations, and development guidelines.",
|
|
||||||
"logo": {
|
|
||||||
"dark": "/logo/dark.png",
|
|
||||||
"light": "/logo/light.png"
|
|
||||||
},
|
|
||||||
"favicon": "/favicon.png",
|
|
||||||
"colors": {
|
|
||||||
"primary": "#0069ED",
|
|
||||||
"light": "#4D9CFF",
|
|
||||||
"dark": "#0050B4"
|
|
||||||
},
|
|
||||||
"theme": "maple",
|
|
||||||
"navigation": {
|
|
||||||
"tabs": [
|
|
||||||
{
|
|
||||||
"tab": "API Documentation",
|
|
||||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/bde4833e4f866b1fc9dcdbcc14267351406069a3/swagger/swagger.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"footerSocials": {
|
|
||||||
"twitter": "https://twitter.com/chatwootapp",
|
|
||||||
"github": "https://github.com/chatwoot",
|
|
||||||
"linkedin": "https://www.linkedin.com/company/chatwoot"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 168 KiB |
@@ -119,7 +119,7 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
|
|
||||||
context 'when it is an authenticated user' do
|
context 'when it is an authenticated user' do
|
||||||
it 'shows an account' do
|
it 'shows an account' do
|
||||||
account.update(name: 'new name')
|
account.update(auto_resolve_duration: 30)
|
||||||
|
|
||||||
get "/api/v1/accounts/#{account.id}",
|
get "/api/v1/accounts/#{account.id}",
|
||||||
headers: admin.create_new_auth_token,
|
headers: admin.create_new_auth_token,
|
||||||
@@ -130,6 +130,7 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
expect(response.body).to include(account.locale)
|
expect(response.body).to include(account.locale)
|
||||||
expect(response.body).to include(account.domain)
|
expect(response.body).to include(account.domain)
|
||||||
expect(response.body).to include(account.support_email)
|
expect(response.body).to include(account.support_email)
|
||||||
|
expect(response.body).to include(account.auto_resolve_duration.to_s)
|
||||||
expect(response.body).to include(account.locale)
|
expect(response.body).to include(account.locale)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -188,8 +189,7 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
locale: 'en',
|
locale: 'en',
|
||||||
domain: 'example.com',
|
domain: 'example.com',
|
||||||
support_email: 'care@example.com',
|
support_email: 'care@example.com',
|
||||||
auto_resolve_after: 40,
|
auto_resolve_duration: 40,
|
||||||
auto_resolve_message: 'Auto resolved',
|
|
||||||
timezone: 'Asia/Kolkata',
|
timezone: 'Asia/Kolkata',
|
||||||
industry: 'Technology',
|
industry: 'Technology',
|
||||||
company_size: '1-10'
|
company_size: '1-10'
|
||||||
@@ -206,10 +206,7 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
expect(account.reload.locale).to eq(params[:locale])
|
expect(account.reload.locale).to eq(params[:locale])
|
||||||
expect(account.reload.domain).to eq(params[:domain])
|
expect(account.reload.domain).to eq(params[:domain])
|
||||||
expect(account.reload.support_email).to eq(params[:support_email])
|
expect(account.reload.support_email).to eq(params[:support_email])
|
||||||
|
expect(account.reload.auto_resolve_duration).to eq(params[:auto_resolve_duration])
|
||||||
%w[auto_resolve_after auto_resolve_message].each do |attribute|
|
|
||||||
expect(account.reload.settings[attribute]).to eq(params[attribute.to_sym])
|
|
||||||
end
|
|
||||||
|
|
||||||
%w[timezone industry company_size].each do |attribute|
|
%w[timezone industry company_size].each do |attribute|
|
||||||
expect(account.reload.custom_attributes[attribute]).to eq(params[attribute.to_sym])
|
expect(account.reload.custom_attributes[attribute]).to eq(params[attribute.to_sym])
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user