Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<script>
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasAccounts: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['closeAccountCreateModal'],
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
accountName: '',
|
||||
};
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
accountName: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
uiFlags: 'agents/getUIFlags',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
async addAccount() {
|
||||
try {
|
||||
const account_id = await this.$store.dispatch('accounts/create', {
|
||||
account_name: this.accountName,
|
||||
});
|
||||
this.$emit('closeAccountCreateModal');
|
||||
useAlert(this.$t('CREATE_ACCOUNT.API.SUCCESS_MESSAGE'));
|
||||
window.location = `/app/accounts/${account_id}/dashboard`;
|
||||
} catch (error) {
|
||||
if (error.response.status === 422) {
|
||||
useAlert(this.$t('CREATE_ACCOUNT.API.EXIST_MESSAGE'));
|
||||
} else {
|
||||
useAlert(this.$t('CREATE_ACCOUNT.API.ERROR_MESSAGE'));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<woot-modal :show="show" :on-close="() => $emit('closeAccountCreateModal')">
|
||||
<div class="flex flex-col h-auto overflow-auto">
|
||||
<woot-modal-header
|
||||
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
|
||||
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
|
||||
/>
|
||||
<div v-if="!hasAccounts" class="mx-8 mt-6 mb-0 text-sm">
|
||||
<div class="flex items-center rounded-md alert">
|
||||
<div class="ml-1 mr-3">
|
||||
<fluent-icon icon="warning" />
|
||||
</div>
|
||||
{{ $t('CREATE_ACCOUNT.NO_ACCOUNT_WARNING') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col w-full" @submit.prevent="addAccount">
|
||||
<div class="w-full">
|
||||
<label :class="{ error: v$.accountName.$error }">
|
||||
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
|
||||
<input
|
||||
v-model="accountName"
|
||||
type="text"
|
||||
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
|
||||
@input="v$.accountName.$touch"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-full flex justify-end gap-2 items-center">
|
||||
<NextButton
|
||||
faded
|
||||
slate
|
||||
type="reset"
|
||||
:label="$t('CREATE_ACCOUNT.FORM.CANCEL')"
|
||||
@click.prevent="() => $emit('closeAccountCreateModal')"
|
||||
/>
|
||||
<NextButton
|
||||
type="submit"
|
||||
:label="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
|
||||
:is-loading="uiFlags.isCreating"
|
||||
:disabled="
|
||||
v$.accountName.$invalid ||
|
||||
v$.accountName.$invalid ||
|
||||
uiFlags.isCreating
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</woot-modal>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
|
||||
const EMPTY_SUBSCRIPTION_INFO = {
|
||||
status: null,
|
||||
endsOn: null,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { Banner },
|
||||
setup() {
|
||||
const { isAdmin } = useAdmin();
|
||||
|
||||
const { accountId } = useAccount();
|
||||
|
||||
return {
|
||||
accountId,
|
||||
isAdmin,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
isOnChatwootCloud: 'globalConfig/isOnChatwootCloud',
|
||||
getAccount: 'accounts/getAccount',
|
||||
}),
|
||||
bannerMessage() {
|
||||
return this.$t('GENERAL_SETTINGS.PAYMENT_PENDING');
|
||||
},
|
||||
actionButtonMessage() {
|
||||
return this.$t('GENERAL_SETTINGS.OPEN_BILLING');
|
||||
},
|
||||
shouldShowBanner() {
|
||||
if (!this.isOnChatwootCloud) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.isAdmin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.isPaymentPending();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
routeToBilling() {
|
||||
this.$router.push({
|
||||
name: 'billing_settings_index',
|
||||
params: { accountId: this.accountId },
|
||||
});
|
||||
},
|
||||
isPaymentPending() {
|
||||
const { status, endsOn } = this.getSubscriptionInfo();
|
||||
|
||||
if (status && endsOn) {
|
||||
const now = new Date();
|
||||
if (status === 'past_due' && endsOn < now) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getSubscriptionInfo() {
|
||||
const account = this.getAccount(this.accountId);
|
||||
if (!account) return EMPTY_SUBSCRIPTION_INFO;
|
||||
|
||||
const { custom_attributes: subscription } = account;
|
||||
if (!subscription) return EMPTY_SUBSCRIPTION_INFO;
|
||||
|
||||
const { subscription_status: status, subscription_ends_on: endsOn } =
|
||||
subscription;
|
||||
|
||||
return { status, endsOn: new Date(endsOn) };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<Banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
has-action-button
|
||||
@primary-action="routeToBilling"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script>
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: { Banner },
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'getCurrentUser',
|
||||
}),
|
||||
bannerMessage() {
|
||||
return this.$t('APP_GLOBAL.EMAIL_VERIFICATION_PENDING');
|
||||
},
|
||||
actionButtonMessage() {
|
||||
return this.$t('APP_GLOBAL.RESEND_VERIFICATION_MAIL');
|
||||
},
|
||||
shouldShowBanner() {
|
||||
return !this.currentUser.confirmed;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resendVerificationEmail() {
|
||||
this.$store.dispatch('resendConfirmation');
|
||||
useAlert(this.$t('APP_GLOBAL.EMAIL_VERIFICATION_SENT'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<Banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="alert"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="actionButtonMessage"
|
||||
action-button-icon="i-lucide-mail"
|
||||
has-action-button
|
||||
@primary-action="resendVerificationEmail"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,81 @@
|
||||
<script>
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import { hasAnUpdateAvailable } from './versionCheckHelper';
|
||||
|
||||
export default {
|
||||
components: { Banner },
|
||||
props: {
|
||||
latestChatwootVersion: { type: String, default: '' },
|
||||
},
|
||||
setup() {
|
||||
const { isAdmin } = useAdmin();
|
||||
return {
|
||||
isAdmin,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return { userDismissedBanner: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ globalConfig: 'globalConfig/get' }),
|
||||
updateAvailable() {
|
||||
return hasAnUpdateAvailable(
|
||||
this.latestChatwootVersion,
|
||||
this.globalConfig.appVersion
|
||||
);
|
||||
},
|
||||
bannerMessage() {
|
||||
return this.$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
|
||||
latestChatwootVersion: this.latestChatwootVersion,
|
||||
});
|
||||
},
|
||||
shouldShowBanner() {
|
||||
return (
|
||||
!this.userDismissedBanner &&
|
||||
this.globalConfig.displayManifest &&
|
||||
this.updateAvailable &&
|
||||
!this.isVersionNotificationDismissed(this.latestChatwootVersion) &&
|
||||
this.isAdmin
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isVersionNotificationDismissed(version) {
|
||||
const dismissedVersions =
|
||||
LocalStorage.get(LOCAL_STORAGE_KEYS.DISMISSED_UPDATES) || [];
|
||||
return dismissedVersions.includes(version);
|
||||
},
|
||||
dismissUpdateBanner() {
|
||||
let updatedDismissedItems =
|
||||
LocalStorage.get(LOCAL_STORAGE_KEYS.DISMISSED_UPDATES) || [];
|
||||
if (updatedDismissedItems instanceof Array) {
|
||||
updatedDismissedItems.push(this.latestChatwootVersion);
|
||||
} else {
|
||||
updatedDismissedItems = [this.latestChatwootVersion];
|
||||
}
|
||||
LocalStorage.set(
|
||||
LOCAL_STORAGE_KEYS.DISMISSED_UPDATES,
|
||||
updatedDismissedItems
|
||||
);
|
||||
this.userDismissedBanner = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<Banner
|
||||
v-if="shouldShowBanner"
|
||||
color-scheme="primary"
|
||||
:banner-message="bannerMessage"
|
||||
href-link="https://github.com/chatwoot/chatwoot/releases"
|
||||
:href-link-text="$t('GENERAL_SETTINGS.LEARN_MORE')"
|
||||
has-close-button
|
||||
@close="dismissUpdateBanner"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { hasAnUpdateAvailable } from '../versionCheckHelper';
|
||||
|
||||
describe('#hasAnUpdateAvailable', () => {
|
||||
it('return false if latest version is invalid', () => {
|
||||
expect(hasAnUpdateAvailable('invalid', '1.0.0')).toBe(false);
|
||||
expect(hasAnUpdateAvailable(null, '1.0.0')).toBe(false);
|
||||
expect(hasAnUpdateAvailable(undefined, '1.0.0')).toBe(false);
|
||||
expect(hasAnUpdateAvailable('', '1.0.0')).toBe(false);
|
||||
});
|
||||
|
||||
it('return correct value if latest version is valid', () => {
|
||||
expect(hasAnUpdateAvailable('1.1.0', '1.0.0')).toBe(true);
|
||||
expect(hasAnUpdateAvailable('0.1.0', '1.0.0')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import semver from 'semver';
|
||||
|
||||
export const hasAnUpdateAvailable = (latestVersion, currentVersion) => {
|
||||
if (!semver.valid(latestVersion)) {
|
||||
return false;
|
||||
}
|
||||
return semver.lt(currentVersion, latestVersion);
|
||||
};
|
||||
Reference in New Issue
Block a user