fix: Installation name not showing (#12096)
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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: {},
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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')"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
96
app/javascript/shared/composables/specs/useBranding.spec.js
Normal file
96
app/javascript/shared/composables/specs/useBranding.spec.js
Normal 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.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
26
app/javascript/shared/composables/useBranding.js
Normal file
26
app/javascript/shared/composables/useBranding.js
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
export const useInstallationName = (str, installationName) => {
|
|
||||||
if (str && installationName) {
|
|
||||||
return str.replace(/Chatwoot/g, installationName);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
useInstallationName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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() };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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') }}
|
||||||
|
|||||||
Reference in New Issue
Block a user