feat: Show banner message if Chatwoot update available (#3999)

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Co-authored-by: Fayaz Ahmed <15716057+fayazara@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Muhsin Keloth 2022-03-15 21:09:14 +05:30 committed by GitHub
parent 02dd5ecfab
commit 8c8c5a77c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 156 additions and 12 deletions

View file

@ -1,5 +1,6 @@
<template> <template>
<div id="app" class="app-wrapper app-root"> <div id="app" class="app-wrapper app-root">
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<router-view></router-view> <router-view></router-view>
</transition> </transition>
@ -13,24 +14,27 @@
</template> </template>
<script> <script>
import { accountIdFromPathname } from './helper/URLHelper';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal'; import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal';
import WootSnackbarBox from './components/SnackbarContainer';
import NetworkNotification from './components/NetworkNotification'; import NetworkNotification from './components/NetworkNotification';
import { accountIdFromPathname } from './helper/URLHelper'; import UpdateBanner from './components/app/UpdateBanner.vue';
import WootSnackbarBox from './components/SnackbarContainer';
export default { export default {
name: 'App', name: 'App',
components: { components: {
WootSnackbarBox,
AddAccountModal, AddAccountModal,
NetworkNotification, NetworkNotification,
UpdateBanner,
WootSnackbarBox,
}, },
data() { data() {
return { return {
showAddAccountModal: false, showAddAccountModal: false,
latestChatwootVersion: null,
}; };
}, },
@ -38,6 +42,7 @@ export default {
...mapGetters({ ...mapGetters({
getAccount: 'accounts/getAccount', getAccount: 'accounts/getAccount',
currentUser: 'getCurrentUser', currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
}), }),
hasAccounts() { hasAccounts() {
return ( return (
@ -72,8 +77,12 @@ export default {
if (accountId) { if (accountId) {
await this.$store.dispatch('accounts/get'); await this.$store.dispatch('accounts/get');
const { locale } = this.getAccount(accountId); const {
locale,
latest_chatwoot_version: latestChatwootVersion,
} = this.getAccount(accountId);
this.setLocale(locale); this.setLocale(locale);
this.latestChatwootVersion = latestChatwootVersion;
} }
}, },
}, },
@ -82,6 +91,11 @@ export default {
<style lang="scss"> <style lang="scss">
@import './assets/scss/app'; @import './assets/scss/app';
.update-banner {
height: var(--space-larger);
align-items: center;
font-size: var(--font-size-small) !important;
}
</style> </style>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style> <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

View file

@ -10,10 +10,24 @@ body {
.app-wrapper { .app-wrapper {
@include full-height; @include full-height;
flex-grow: 0;
min-height: 0;
width: 100%; width: 100%;
} }
.app-root { .banner + .app-wrapper {
.button--fixed-right-top {
top: 5.6 * $space-one;
}
.off-canvas-content {
.button--fixed-right-top {
top: $space-small;
}
}
}
is-closed .app-root {
@include flex; @include flex;
flex-direction: column; flex-direction: column;
} }
@ -21,6 +35,7 @@ body {
.app-content { .app-content {
@include flex; @include flex;
@include full-height; @include full-height;
min-height: 0;
overflow: hidden; overflow: hidden;
} }

View file

@ -0,0 +1,74 @@
<template>
<banner
v-if="shouldShowBanner"
class="update-banner"
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>
<script>
import Banner from 'dashboard/components/ui/Banner.vue';
import LocalStorage from '../../helper/localStorage';
import { mapGetters } from 'vuex';
import adminMixin from 'dashboard/mixins/isAdmin';
const semver = require('semver');
const dismissedUpdates = new LocalStorage('dismissedUpdates');
export default {
components: {
Banner,
},
mixins: [adminMixin],
props: {
latestChatwootVersion: {
type: String,
default: '',
},
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
hasAnUpdateAvailable() {
if (!semver.valid(this.latestChatwootVersion)) {
return false;
}
return semver.lt(
this.globalConfig.appVersion,
this.latestChatwootVersion
);
},
bannerMessage() {
return this.$t('GENERAL_SETTINGS.UPDATE_CHATWOOT', {
latestChatwootVersion: this.latestChatwootVersion,
});
},
shouldShowBanner() {
return (
this.globalConfig.displayManifest &&
this.hasAnUpdateAvailable &&
!this.isVersionNotificationDismissed(this.latestChatwootVersion) &&
this.isAdmin
);
},
},
methods: {
isVersionNotificationDismissed(version) {
return dismissedUpdates.get().includes(version);
},
dismissUpdateBanner() {
let updatedDismissedItems = dismissedUpdates.get();
if (updatedDismissedItems instanceof Array) {
updatedDismissedItems.push(this.latestChatwootVersion);
} else {
updatedDismissedItems = [this.latestChatwootVersion];
}
dismissedUpdates.store(updatedDismissedItems);
this.latestChatwootVersion = this.globalConfig.appVersion;
},
},
};
</script>

View file

@ -184,6 +184,8 @@ export default {
.woot-sidebar { .woot-sidebar {
background: var(--white); background: var(--white);
display: flex; display: flex;
min-height: 0;
height: 100%;
} }
</style> </style>

View file

@ -93,7 +93,7 @@ export default {
width: var(--space-jumbo); width: var(--space-jumbo);
border-right: 1px solid var(--s-50); border-right: 1px solid var(--s-50);
box-sizing: content-box; box-sizing: content-box;
height: 100vh; height: 100%;
flex-shrink: 0; flex-shrink: 0;
} }

View file

@ -225,7 +225,7 @@ export default {
.secondary-menu { .secondary-menu {
background: var(--white); background: var(--white);
border-right: 1px solid var(--s-50); border-right: 1px solid var(--s-50);
height: 100vh; height: 100%;
width: 19rem; width: 19rem;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="banner" :class="bannerClasses"> <div class="banner" :class="bannerClasses">
<span> <span class="banner-message">
{{ bannerMessage }} {{ bannerMessage }}
<a <a
v-if="hrefLink" v-if="hrefLink"
@ -26,7 +26,7 @@
v-if="hasCloseButton" v-if="hasCloseButton"
size="small" size="small"
variant="link" variant="link"
color-scheme="warning" color-scheme="secondary"
icon="dismiss-circle" icon="dismiss-circle"
class-names="banner-action__button" class-names="banner-action__button"
@click="onClickClose" @click="onClickClose"
@ -92,8 +92,19 @@ export default {
justify-content: center; justify-content: center;
position: sticky; position: sticky;
&.primary {
background: var(--w-500);
.banner-action__button {
color: var(--white);
}
}
&.secondary { &.secondary {
background: var(--s-300); background: var(--s-200);
color: var(--s-800);
a {
color: var(--s-800);
}
} }
&.alert { &.alert {
@ -110,9 +121,13 @@ export default {
&.gray { &.gray {
background: var(--b-500); background: var(--b-500);
.banner-action__button {
color: var(--white);
}
} }
a { a {
margin-left: var(--space-smaller);
text-decoration: underline; text-decoration: underline;
color: var(--white); color: var(--white);
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
@ -125,5 +140,10 @@ export default {
white-space: nowrap; white-space: nowrap;
} }
} }
.banner-message {
display: flex;
align-items: center;
}
} }
</style> </style>

View file

@ -100,7 +100,8 @@ export default {
display: flex; display: flex;
background: var(--color-background-light); background: var(--color-background-light);
margin: 0; margin: 0;
height: calc(100vh - var(--space-jumbo)); height: 100%;
min-height: 0;
} }
.conversation-sidebar-wrap { .conversation-sidebar-wrap {

View file

@ -0,0 +1,17 @@
class LocalStorage {
constructor(key) {
this.key = key;
}
store(allItems) {
localStorage.setItem(this.key, JSON.stringify(allItems));
localStorage.setItem(this.key + ':ts', Date.now());
}
get() {
let stored = localStorage.getItem(this.key);
return JSON.parse(stored) || [];
}
}
export default LocalStorage;

View file

@ -47,7 +47,8 @@
"CUSTOM_EMAIL_DOMAIN_ENABLED": "You can receive emails in your custom domain now." "CUSTOM_EMAIL_DOMAIN_ENABLED": "You can receive emails in your custom domain now."
} }
}, },
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance." "UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance.",
"LEARN_MORE":"Learn more"
}, },
"FORMS": { "FORMS": {
"MULTISELECT": { "MULTISELECT": {