fix: Installation name not showing (#12096)

This commit is contained in:
Sivin Varghese
2025-08-06 13:11:22 +05:30
committed by GitHub
parent 855dd590ab
commit d5286c9535
23 changed files with 215 additions and 127 deletions

View File

@@ -1,9 +1,5 @@
<script> <script>
/* eslint no-console: 0 */
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
export default { export default {
mixins: [globalConfigMixin],
props: { props: {
items: { items: {
type: Array, type: Array,

View File

@@ -3,7 +3,7 @@ import WidgetHead from './WidgetHead.vue';
import WidgetBody from './WidgetBody.vue'; import WidgetBody from './WidgetBody.vue';
import WidgetFooter from './WidgetFooter.vue'; import WidgetFooter from './WidgetFooter.vue';
import InputRadioGroup from 'dashboard/routes/dashboard/settings/inbox/components/InputRadioGroup.vue'; import InputRadioGroup from 'dashboard/routes/dashboard/settings/inbox/components/InputRadioGroup.vue';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
@@ -14,7 +14,6 @@ export default {
WidgetFooter, WidgetFooter,
InputRadioGroup, InputRadioGroup,
}, },
mixins: [globalConfigMixin],
props: { props: {
welcomeHeading: { welcomeHeading: {
type: String, type: String,
@@ -57,6 +56,12 @@ export default {
default: '', default: '',
}, },
}, },
setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
data() { data() {
return { return {
widgetScreens: [ widgetScreens: [
@@ -159,9 +164,8 @@ export default {
/> />
<span> <span>
{{ {{
useInstallationName( replaceInstallationName(
$t('INBOX_MGMT.WIDGET_BUILDER.BRANDING_TEXT'), $t('INBOX_MGMT.WIDGET_BUILDER.BRANDING_TEXT')
globalConfig.installationName
) )
}} }}
</span> </span>

View File

@@ -3,14 +3,19 @@ import ChannelItem from 'dashboard/components/widgets/ChannelItem.vue';
import router from '../../../index'; import router from '../../../index';
import PageHeader from '../SettingsSubPageHeader.vue'; import PageHeader from '../SettingsSubPageHeader.vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
export default { export default {
components: { components: {
ChannelItem, ChannelItem,
PageHeader, PageHeader,
}, },
mixins: [globalConfigMixin], setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
data() { data() {
return { return {
enabledFeatures: {}, enabledFeatures: {},
@@ -69,12 +74,7 @@ export default {
<PageHeader <PageHeader
class="max-w-4xl" class="max-w-4xl"
:header-title="$t('INBOX_MGMT.ADD.AUTH.TITLE')" :header-title="$t('INBOX_MGMT.ADD.AUTH.TITLE')"
:header-content=" :header-content="replaceInstallationName($t('INBOX_MGMT.ADD.AUTH.DESC'))"
useInstallationName(
$t('INBOX_MGMT.ADD.AUTH.DESC'),
globalConfig.installationName
)
"
/> />
<div <div
class="grid max-w-3xl grid-cols-2 mx-0 mt-6 sm:grid-cols-3 lg:grid-cols-4" class="grid max-w-3xl grid-cols-2 mx-0 mt-6 sm:grid-cols-3 lg:grid-cols-4"

View File

@@ -1,9 +1,14 @@
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
export default { export default {
mixins: [globalConfigMixin], setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
computed: { computed: {
...mapGetters({ ...mapGetters({
globalConfig: 'globalConfig/get', globalConfig: 'globalConfig/get',
@@ -29,10 +34,7 @@ export default {
items() { items() {
return this.createFlowSteps.map(item => ({ return this.createFlowSteps.map(item => ({
...item, ...item,
body: this.useInstallationName( body: this.replaceInstallationName(item.body),
item.body,
this.globalConfig.installationName
),
})); }));
}, },
}, },

View File

@@ -6,11 +6,11 @@ import { useAlert } from 'dashboard/composables';
import { useAccount } from 'dashboard/composables/useAccount'; import { useAccount } from 'dashboard/composables/useAccount';
import { required } from '@vuelidate/validators'; import { required } from '@vuelidate/validators';
import LoadingState from 'dashboard/components/widgets/LoadingState.vue'; import LoadingState from 'dashboard/components/widgets/LoadingState.vue';
import { mapGetters } from 'vuex';
import ChannelApi from '../../../../../api/channels'; import ChannelApi from '../../../../../api/channels';
import PageHeader from '../../SettingsSubPageHeader.vue'; import PageHeader from '../../SettingsSubPageHeader.vue';
import router from '../../../../index'; import router from '../../../../index';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import NextButton from 'dashboard/components-next/button/Button.vue'; import NextButton from 'dashboard/components-next/button/Button.vue';
import { loadScript } from 'dashboard/helper/DOMHelpers'; import { loadScript } from 'dashboard/helper/DOMHelpers';
@@ -22,11 +22,12 @@ export default {
PageHeader, PageHeader,
NextButton, NextButton,
}, },
mixins: [globalConfigMixin],
setup() { setup() {
const { accountId } = useAccount(); const { accountId } = useAccount();
const { replaceInstallationName } = useBranding();
return { return {
accountId, accountId,
replaceInstallationName,
v$: useVuelidate(), v$: useVuelidate(),
}; };
}, },
@@ -66,9 +67,6 @@ export default {
getSelectablePages() { getSelectablePages() {
return this.pageList.filter(item => !item.exists); return this.pageList.filter(item => !item.exists);
}, },
...mapGetters({
globalConfig: 'globalConfig/get',
}),
}, },
mounted() { mounted() {
@@ -223,12 +221,7 @@ export default {
/> />
</a> </a>
<p class="py-6"> <p class="py-6">
{{ {{ replaceInstallationName($t('INBOX_MGMT.ADD.FB.HELP')) }}
useInstallationName(
$t('INBOX_MGMT.ADD.FB.HELP'),
globalConfig.installationName
)
}}
</p> </p>
</div> </div>
<div v-else> <div v-else>
@@ -249,10 +242,7 @@ export default {
<PageHeader <PageHeader
:header-title="$t('INBOX_MGMT.ADD.DETAILS.TITLE')" :header-title="$t('INBOX_MGMT.ADD.DETAILS.TITLE')"
:header-content=" :header-content="
useInstallationName( replaceInstallationName($t('INBOX_MGMT.ADD.DETAILS.DESC'))
$t('INBOX_MGMT.ADD.DETAILS.DESC'),
globalConfig.installationName
)
" "
/> />
</div> </div>

View File

@@ -1,11 +1,9 @@
<script> <script>
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { useAccount } from 'dashboard/composables/useAccount'; import { useAccount } from 'dashboard/composables/useAccount';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import instagramClient from 'dashboard/api/channel/instagramClient'; import instagramClient from 'dashboard/api/channel/instagramClient';
export default { export default {
mixins: [globalConfigMixin],
setup() { setup() {
const { accountId } = useAccount(); const { accountId } = useAccount();
return { return {

View File

@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import DashboardAppModal from './DashboardAppModal.vue'; import DashboardAppModal from './DashboardAppModal.vue';
import DashboardAppsRow from './DashboardAppsRow.vue'; import DashboardAppsRow from './DashboardAppsRow.vue';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import BaseSettingsHeader from '../../components/BaseSettingsHeader.vue'; import BaseSettingsHeader from '../../components/BaseSettingsHeader.vue';
import NextButton from 'dashboard/components-next/button/Button.vue'; import NextButton from 'dashboard/components-next/button/Button.vue';
@@ -14,7 +13,6 @@ export default {
DashboardAppsRow, DashboardAppsRow,
NextButton, NextButton,
}, },
mixins: [globalConfigMixin],
data() { data() {
return { return {
loading: {}, loading: {},

View File

@@ -1,12 +1,14 @@
<script setup> <script setup>
import { useStoreGetters, useStore } from 'dashboard/composables/store'; import { useStoreGetters, useStore } from 'dashboard/composables/store';
import { computed, onMounted } from 'vue'; import { computed, onMounted } from 'vue';
import { useBranding } from 'shared/composables/useBranding';
import IntegrationItem from './IntegrationItem.vue'; import IntegrationItem from './IntegrationItem.vue';
import SettingsLayout from '../SettingsLayout.vue'; import SettingsLayout from '../SettingsLayout.vue';
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue'; import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
const store = useStore(); const store = useStore();
const getters = useStoreGetters(); const getters = useStoreGetters();
const { replaceInstallationName } = useBranding();
const uiFlags = getters['integrations/getUIFlags']; const uiFlags = getters['integrations/getUIFlags'];
@@ -27,7 +29,9 @@ onMounted(() => {
<template #header> <template #header>
<BaseSettingsHeader <BaseSettingsHeader
:title="$t('INTEGRATION_SETTINGS.HEADER')" :title="$t('INTEGRATION_SETTINGS.HEADER')"
:description="$t('INTEGRATION_SETTINGS.DESCRIPTION')" :description="
replaceInstallationName($t('INTEGRATION_SETTINGS.DESCRIPTION'))
"
:link-text="$t('INTEGRATION_SETTINGS.LEARN_MORE')" :link-text="$t('INTEGRATION_SETTINGS.LEARN_MORE')"
feature-name="integrations" feature-name="integrations"
/> />

View File

@@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { frontendURL } from '../../../../helper/URLHelper'; import { frontendURL } from '../../../../helper/URLHelper';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useInstallationName } from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
@@ -26,11 +26,11 @@ const props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const store = useStore(); const store = useStore();
const router = useRouter(); const router = useRouter();
const { replaceInstallationName } = useBranding();
const dialogRef = ref(null); const dialogRef = ref(null);
const accountId = computed(() => store.getters.getCurrentAccountId); const accountId = computed(() => store.getters.getCurrentAccountId);
const globalConfig = computed(() => store.getters['globalConfig/get']);
const openDeletePopup = () => { const openDeletePopup = () => {
if (dialogRef.value) { if (dialogRef.value) {
@@ -82,12 +82,7 @@ const confirmDeletion = () => {
{{ integrationName }} {{ integrationName }}
</h3> </h3>
<p class="text-n-slate-11 text-sm leading-6"> <p class="text-n-slate-11 text-sm leading-6">
{{ {{ replaceInstallationName(integrationDescription) }}
useInstallationName(
integrationDescription,
globalConfig.installationName
)
}}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { useStoreGetters } from 'dashboard/composables/store'; import { useStoreGetters } from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { frontendURL } from 'dashboard/helper/URLHelper'; import { frontendURL } from 'dashboard/helper/URLHelper';
import { useInstallationName } from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
@@ -28,9 +28,9 @@ const props = defineProps({
const getters = useStoreGetters(); const getters = useStoreGetters();
const accountId = getters.getCurrentAccountId; const accountId = getters.getCurrentAccountId;
const globalConfig = getters['globalConfig/get'];
const { t } = useI18n(); const { t } = useI18n();
const { replaceInstallationName } = useBranding();
const integrationStatus = computed(() => const integrationStatus = computed(() =>
props.enabled props.enabled
@@ -80,7 +80,7 @@ const actionURL = computed(() =>
</router-link> </router-link>
</div> </div>
<p class="text-n-slate-11"> <p class="text-n-slate-11">
{{ useInstallationName(description, globalConfig.installationName) }} {{ replaceInstallationName(description) }}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,4 @@
<script> <script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import Integration from './Integration.vue'; import Integration from './Integration.vue';
import IntegrationHelpText from './IntegrationHelpText.vue'; import IntegrationHelpText from './IntegrationHelpText.vue';
@@ -8,8 +7,6 @@ export default {
Integration, Integration,
IntegrationHelpText, IntegrationHelpText,
}, },
mixins: [globalConfigMixin],
props: { props: {
integrationId: { integrationId: {
type: [String, Number], type: [String, Number],

View File

@@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useInstallationName } from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter'; import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
@@ -18,6 +18,7 @@ const store = useStore();
const { t } = useI18n(); const { t } = useI18n();
const { formatMessage } = useMessageFormatter(); const { formatMessage } = useMessageFormatter();
const { replaceInstallationName } = useBranding();
const selectedChannelId = ref(''); const selectedChannelId = ref('');
const availableChannels = ref([]); const availableChannels = ref([]);
@@ -29,16 +30,9 @@ const errorDescription = computed(() => {
? t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION') ? t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.DESCRIPTION')
: t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED'); : t('INTEGRATION_SETTINGS.SLACK.SELECT_CHANNEL.EXPIRED');
}); });
const globalConfig = computed(() => store.getters['globalConfig/get']);
const formattedErrorMessage = computed(() => { const formattedErrorMessage = computed(() => {
return formatMessage( return formatMessage(replaceInstallationName(errorDescription.value), false);
useInstallationName(
errorDescription.value,
globalConfig.value.installationName
),
false
);
}); });
const fetchChannels = async () => { const fetchChannels = async () => {

View File

@@ -1,6 +1,7 @@
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useBranding } from 'shared/composables/useBranding';
import NextButton from 'dashboard/components-next/button/Button.vue'; import NextButton from 'dashboard/components-next/button/Button.vue';
import NewWebhook from './NewWebHook.vue'; import NewWebhook from './NewWebHook.vue';
import EditWebhook from './EditWebHook.vue'; import EditWebhook from './EditWebHook.vue';
@@ -17,6 +18,10 @@ export default {
EditWebhook, EditWebhook,
WebhookRow, WebhookRow,
}, },
setup() {
const { replaceInstallationName } = useBranding();
return { replaceInstallationName };
},
data() { data() {
return { return {
loading: {}, loading: {},
@@ -99,7 +104,7 @@ export default {
<BaseSettingsHeader <BaseSettingsHeader
v-if="integration.name" v-if="integration.name"
:title="integration.name" :title="integration.name"
:description="integration.description" :description="replaceInstallationName(integration.description)"
:link-text="$t('INTEGRATION_SETTINGS.WEBHOOK.LEARN_MORE')" :link-text="$t('INTEGRATION_SETTINGS.WEBHOOK.LEARN_MORE')"
feature-name="webhook" feature-name="webhook"
:back-button-label="$t('INTEGRATION_SETTINGS.HEADER')" :back-button-label="$t('INTEGRATION_SETTINGS.HEADER')"

View File

@@ -1,21 +1,25 @@
<script> <script>
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import WebhookForm from './WebhookForm.vue'; import WebhookForm from './WebhookForm.vue';
export default { export default {
components: { WebhookForm }, components: { WebhookForm },
mixins: [globalConfigMixin],
props: { props: {
onClose: { onClose: {
type: Function, type: Function,
required: true, required: true,
}, },
}, },
setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
computed: { computed: {
...mapGetters({ ...mapGetters({
globalConfig: 'globalConfig/get',
uiFlags: 'webhooks/getUIFlags', uiFlags: 'webhooks/getUIFlags',
}), }),
}, },
@@ -43,10 +47,7 @@ export default {
<woot-modal-header <woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.TITLE')" :header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.TITLE')"
:header-content=" :header-content="
useInstallationName( replaceInstallationName($t('INTEGRATION_SETTINGS.WEBHOOK.FORM.DESC'))
$t('INTEGRATION_SETTINGS.WEBHOOK.FORM.DESC'),
globalConfig.installationName
)
" "
/> />
<WebhookForm <WebhookForm

View File

@@ -3,10 +3,10 @@ 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 { useFontSize } from 'dashboard/composables/useFontSize'; import { useFontSize } from 'dashboard/composables/useFontSize';
import { useBranding } from 'shared/composables/useBranding';
import { clearCookiesOnLogout } from 'dashboard/store/utils/api.js'; import { clearCookiesOnLogout } from 'dashboard/store/utils/api.js';
import { copyTextToClipboard } from 'shared/helpers/clipboard'; import { copyTextToClipboard } from 'shared/helpers/clipboard';
import { parseAPIErrorResponse } from 'dashboard/store/utils/api'; import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import UserProfilePicture from './UserProfilePicture.vue'; import UserProfilePicture from './UserProfilePicture.vue';
import UserBasicDetails from './UserBasicDetails.vue'; import UserBasicDetails from './UserBasicDetails.vue';
import MessageSignature from './MessageSignature.vue'; import MessageSignature from './MessageSignature.vue';
@@ -37,16 +37,17 @@ export default {
AudioNotifications, AudioNotifications,
AccessToken, AccessToken,
}, },
mixins: [globalConfigMixin],
setup() { setup() {
const { isEditorHotKeyEnabled, updateUISettings } = useUISettings(); const { isEditorHotKeyEnabled, updateUISettings } = useUISettings();
const { currentFontSize, updateFontSize } = useFontSize(); const { currentFontSize, updateFontSize } = useFontSize();
const { replaceInstallationName } = useBranding();
return { return {
currentFontSize, currentFontSize,
updateFontSize, updateFontSize,
isEditorHotKeyEnabled, isEditorHotKeyEnabled,
updateUISettings, updateUISettings,
replaceInstallationName,
}; };
}, },
data() { data() {
@@ -215,7 +216,11 @@ export default {
</div> </div>
<FormSection <FormSection
:title="$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.TITLE')" :title="$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.TITLE')"
:description="$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.NOTE')" :description="
replaceInstallationName(
$t('PROFILE_SETTINGS.FORM.INTERFACE_SECTION.NOTE')
)
"
> >
<FontSize <FontSize
:value="currentFontSize" :value="currentFontSize"
@@ -288,10 +293,7 @@ export default {
<FormSection <FormSection
:title="$t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.TITLE')" :title="$t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.TITLE')"
:description=" :description="
useInstallationName( replaceInstallationName($t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.NOTE'))
$t('PROFILE_SETTINGS.FORM.ACCESS_TOKEN.NOTE'),
globalConfig.installationName
)
" "
> >
<AccessToken <AccessToken

View File

@@ -1,5 +1,5 @@
<script> <script>
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
const { const {
LOGO_THUMBNAIL: logoThumbnail, LOGO_THUMBNAIL: logoThumbnail,
@@ -8,13 +8,18 @@ const {
} = window.globalConfig || {}; } = window.globalConfig || {};
export default { export default {
mixins: [globalConfigMixin],
props: { props: {
disableBranding: { disableBranding: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
setup() {
const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
};
},
data() { data() {
return { return {
globalConfig: { globalConfig: {
@@ -61,7 +66,7 @@ export default {
:src="globalConfig.logoThumbnail" :src="globalConfig.logoThumbnail"
/> />
<span> <span>
{{ useInstallationName($t('POWERED_BY'), globalConfig.brandName) }} {{ replaceInstallationName($t('POWERED_BY')) }}
</span> </span>
</a> </a>
</div> </div>

View File

@@ -0,0 +1,96 @@
import { useBranding } from '../useBranding';
import { useMapGetter } from 'dashboard/composables/store.js';
// Mock the store composable
vi.mock('dashboard/composables/store.js', () => ({
useMapGetter: vi.fn(),
}));
describe('useBranding', () => {
let mockGlobalConfig;
beforeEach(() => {
mockGlobalConfig = {
value: {
installationName: 'MyCompany',
},
};
useMapGetter.mockReturnValue(mockGlobalConfig);
});
afterEach(() => {
vi.clearAllMocks();
});
describe('replaceInstallationName', () => {
it('should replace "Chatwoot" with installation name when both text and installation name are provided', () => {
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName('Welcome to Chatwoot');
expect(result).toBe('Welcome to MyCompany');
});
it('should replace multiple occurrences of "Chatwoot"', () => {
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName(
'Chatwoot is great! Use Chatwoot today.'
);
expect(result).toBe('MyCompany is great! Use MyCompany today.');
});
it('should return original text when installation name is not provided', () => {
mockGlobalConfig.value = {};
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName('Welcome to Chatwoot');
expect(result).toBe('Welcome to Chatwoot');
});
it('should return original text when globalConfig is not available', () => {
mockGlobalConfig.value = undefined;
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName('Welcome to Chatwoot');
expect(result).toBe('Welcome to Chatwoot');
});
it('should return original text when text is empty or null', () => {
const { replaceInstallationName } = useBranding();
expect(replaceInstallationName('')).toBe('');
expect(replaceInstallationName(null)).toBe(null);
expect(replaceInstallationName(undefined)).toBe(undefined);
});
it('should handle text without "Chatwoot" gracefully', () => {
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName('Welcome to our platform');
expect(result).toBe('Welcome to our platform');
});
it('should be case-sensitive for "Chatwoot"', () => {
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName(
'Welcome to chatwoot and CHATWOOT'
);
expect(result).toBe('Welcome to chatwoot and CHATWOOT');
});
it('should handle special characters in installation name', () => {
mockGlobalConfig.value = {
installationName: 'My-Company & Co.',
};
const { replaceInstallationName } = useBranding();
const result = replaceInstallationName('Welcome to Chatwoot');
expect(result).toBe('Welcome to My-Company & Co.');
});
});
});

View File

@@ -0,0 +1,26 @@
/**
* Composable for branding-related utilities
* Provides methods to customize text with installation-specific branding
*/
import { useMapGetter } from 'dashboard/composables/store.js';
export function useBranding() {
const globalConfig = useMapGetter('globalConfig/get');
/**
* Replaces "Chatwoot" in text with the installation name from global config
* @param {string} text - The text to process
* @returns {string} - Text with "Chatwoot" replaced by installation name
*/
const replaceInstallationName = text => {
if (!text) return text;
const installationName = globalConfig.value?.installationName;
if (!installationName) return text;
return text.replace(/Chatwoot/g, installationName);
};
return {
replaceInstallationName,
};
}

View File

@@ -1,12 +0,0 @@
export const useInstallationName = (str, installationName) => {
if (str && installationName) {
return str.replace(/Chatwoot/g, installationName);
}
return str;
};
export default {
methods: {
useInstallationName,
},
};

View File

@@ -1,18 +1,17 @@
<script> <script>
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { required, minLength, email } from '@vuelidate/validators'; import { required, minLength, email } from '@vuelidate/validators';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import FormInput from '../../../../components/Form/Input.vue'; import FormInput from '../../../../components/Form/Input.vue';
import { resetPassword } from '../../../../api/auth'; import { resetPassword } from '../../../../api/auth';
import NextButton from 'dashboard/components-next/button/Button.vue'; import NextButton from 'dashboard/components-next/button/Button.vue';
export default { export default {
components: { FormInput, NextButton }, components: { FormInput, NextButton },
mixins: [globalConfigMixin],
setup() { setup() {
return { v$: useVuelidate() }; const { replaceInstallationName } = useBranding();
return { v$: useVuelidate(), replaceInstallationName };
}, },
data() { data() {
return { return {
@@ -24,9 +23,6 @@ export default {
error: '', error: '',
}; };
}, },
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
},
validations() { validations() {
return { return {
credentials: { credentials: {
@@ -82,12 +78,7 @@ export default {
<p <p
class="mb-4 text-sm font-normal leading-6 tracking-normal text-n-slate-11" class="mb-4 text-sm font-normal leading-6 tracking-normal text-n-slate-11"
> >
{{ {{ replaceInstallationName($t('RESET_PASSWORD.DESCRIPTION')) }}
useInstallationName(
$t('RESET_PASSWORD.DESCRIPTION'),
globalConfig.installationName
)
}}
</p> </p>
<div class="space-y-5"> <div class="space-y-5">
<FormInput <FormInput

View File

@@ -1,6 +1,6 @@
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import globalConfigMixin from 'shared/mixins/globalConfigMixin'; import { useBranding } from 'shared/composables/useBranding';
import SignupForm from './components/Signup/Form.vue'; import SignupForm from './components/Signup/Form.vue';
import Testimonials from './components/Testimonials/Index.vue'; import Testimonials from './components/Testimonials/Index.vue';
import Spinner from 'shared/components/Spinner.vue'; import Spinner from 'shared/components/Spinner.vue';
@@ -11,7 +11,10 @@ export default {
Spinner, Spinner,
Testimonials, Testimonials,
}, },
mixins: [globalConfigMixin], setup() {
const { replaceInstallationName } = useBranding();
return { replaceInstallationName };
},
data() { data() {
return { isLoading: false }; return { isLoading: false };
}, },
@@ -61,12 +64,7 @@ export default {
<div class="px-1 text-sm text-n-slate-12"> <div class="px-1 text-sm text-n-slate-12">
<span>{{ $t('REGISTER.HAVE_AN_ACCOUNT') }}</span> <span>{{ $t('REGISTER.HAVE_AN_ACCOUNT') }}</span>
<router-link class="text-link text-n-brand" to="/app/login"> <router-link class="text-link text-n-brand" to="/app/login">
{{ {{ replaceInstallationName($t('LOGIN.TITLE')) }}
useInstallationName(
$t('LOGIN.TITLE'),
globalConfig.installationName
)
}}
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,6 @@ import { useVuelidate } from '@vuelidate/core';
import { required, minLength, email } from '@vuelidate/validators'; import { required, minLength, email } from '@vuelidate/validators';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals'; import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'; import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import FormInput from '../../../../../components/Form/Input.vue'; import FormInput from '../../../../../components/Form/Input.vue';
@@ -20,7 +19,6 @@ export default {
NextButton, NextButton,
VueHcaptcha, VueHcaptcha,
}, },
mixins: [globalConfigMixin],
setup() { setup() {
return { v$: useVuelidate() }; return { v$: useVuelidate() };
}, },

View File

@@ -8,8 +8,7 @@ import { required, email } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage'; import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage';
import SessionStorage from 'shared/helpers/sessionStorage'; import SessionStorage from 'shared/helpers/sessionStorage';
// mixins import { useBranding } from 'shared/composables/useBranding';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
// components // components
import FormInput from '../../components/Form/Input.vue'; import FormInput from '../../components/Form/Input.vue';
@@ -31,7 +30,6 @@ export default {
Spinner, Spinner,
NextButton, NextButton,
}, },
mixins: [globalConfigMixin],
props: { props: {
ssoAuthToken: { type: String, default: '' }, ssoAuthToken: { type: String, default: '' },
ssoAccountId: { type: String, default: '' }, ssoAccountId: { type: String, default: '' },
@@ -40,7 +38,11 @@ export default {
authError: { type: String, default: '' }, authError: { type: String, default: '' },
}, },
setup() { setup() {
return { v$: useVuelidate() }; const { replaceInstallationName } = useBranding();
return {
replaceInstallationName,
v$: useVuelidate(),
};
}, },
data() { data() {
return { return {
@@ -182,9 +184,7 @@ export default {
class="hidden w-auto h-8 mx-auto dark:block" class="hidden w-auto h-8 mx-auto dark:block"
/> />
<h2 class="mt-6 text-3xl font-medium text-center text-n-slate-12"> <h2 class="mt-6 text-3xl font-medium text-center text-n-slate-12">
{{ {{ replaceInstallationName($t('LOGIN.TITLE')) }}
useInstallationName($t('LOGIN.TITLE'), globalConfig.installationName)
}}
</h2> </h2>
<p v-if="showSignupLink" class="mt-3 text-sm text-center text-n-slate-11"> <p v-if="showSignupLink" class="mt-3 text-sm text-center text-n-slate-11">
{{ $t('COMMON.OR') }} {{ $t('COMMON.OR') }}