Migrate widgetPortalArticlesToPinia
This commit is contained in:
@@ -10,6 +10,7 @@ import router from '../widget/router';
|
||||
import { directive as onClickaway } from 'vue3-click-away';
|
||||
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
|
||||
import { plugin, defaultConfig } from '@formkit/vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
import {
|
||||
startsWithPlus,
|
||||
@@ -21,9 +22,11 @@ const i18n = createI18n({
|
||||
locale: 'en',
|
||||
messages: i18nMessages,
|
||||
});
|
||||
const pinia = createPinia();
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(i18n);
|
||||
app.use(pinia);
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import endPoints from 'widget/api/endPoints';
|
||||
import { API } from 'widget/helpers/axios';
|
||||
|
||||
export const getMostReadArticles = async (slug, locale) => {
|
||||
const urlData = endPoints.getMostReadArticles(slug, locale);
|
||||
return API.get(urlData.url, { params: urlData.params });
|
||||
};
|
||||
@@ -97,15 +97,6 @@ const triggerCampaign = ({ websiteToken, campaignId, customAttributes }) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const getMostReadArticles = (slug, locale) => ({
|
||||
url: `/hc/${slug}/${locale}/articles.json`,
|
||||
params: {
|
||||
page: 1,
|
||||
sort: 'views',
|
||||
status: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createConversation,
|
||||
sendMessage,
|
||||
@@ -115,5 +106,4 @@ export default {
|
||||
getAvailableAgents,
|
||||
getCampaigns,
|
||||
triggerCampaign,
|
||||
getMostReadArticles,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import ArticleBlock from 'widget/components/pageComponents/Home/Article/ArticleBlock.vue';
|
||||
import ArticleCardSkeletonLoader from 'widget/components/pageComponents/Home/Article/SkeletonLoader.vue';
|
||||
import { useArticleStore } from 'widget/stores/articleStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
const store = useArticleStore();
|
||||
const router = useRouter();
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
|
||||
const portal = computed(() => window.chatwootWebChannel.portal);
|
||||
|
||||
const locale = computed(() => {
|
||||
const { locale: selectedLocale } = useI18n();
|
||||
|
||||
const {
|
||||
allowed_locales: allowedLocales,
|
||||
default_locale: defaultLocale = 'en',
|
||||
} = portal.value.config;
|
||||
|
||||
// IMPORTANT: Variation strict locale matching, Follow iso_639_1_code
|
||||
// If the exact match of a locale is available in the list of portal locales, return it
|
||||
// Else return the default locale. Eg: `es` will not work if `es_ES` is available in the list
|
||||
if (allowedLocales.includes(selectedLocale)) {
|
||||
return locale;
|
||||
}
|
||||
return defaultLocale;
|
||||
});
|
||||
|
||||
const fetchArticles = () => {
|
||||
if (portal.value && !store.getRecords.length) {
|
||||
store.index({ slug: portal.value.slug, locale: locale.value });
|
||||
}
|
||||
};
|
||||
|
||||
const openArticleInArticleViewer = link => {
|
||||
let linkToOpen = `${link}?show_plain_layout=true`;
|
||||
if (prefersDarkMode) {
|
||||
linkToOpen = `${linkToOpen}&theme=dark`;
|
||||
}
|
||||
router.push({ name: 'article-viewer', query: { link: linkToOpen } });
|
||||
};
|
||||
const viewAllArticles = () => {
|
||||
const {
|
||||
portal: { slug },
|
||||
} = window.chatwootWebChannel;
|
||||
openArticleInArticleViewer(`/hc/${slug}/${locale.value}`);
|
||||
};
|
||||
|
||||
const hasArticles = computed(
|
||||
() => !store.getUIFlags.isFetching && !!store.getRecords.length
|
||||
);
|
||||
|
||||
onMounted(() => fetchArticles());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="portal"
|
||||
class="w-full shadow outline-1 outline outline-n-container rounded-xl bg-n-solid-2 px-5 py-4"
|
||||
>
|
||||
<ArticleBlock
|
||||
v-if="hasArticles"
|
||||
:articles="store.getRecords"
|
||||
@view="openArticleInArticleViewer"
|
||||
@view-all="viewAllArticles"
|
||||
/>
|
||||
<ArticleCardSkeletonLoader v-else />
|
||||
</div>
|
||||
<div v-else />
|
||||
</template>
|
||||
@@ -10,7 +10,6 @@ import events from 'widget/store/modules/events';
|
||||
import globalConfig from 'shared/store/globalConfig';
|
||||
import message from 'widget/store/modules/message';
|
||||
import campaign from 'widget/store/modules/campaign';
|
||||
import article from 'widget/store/modules/articles';
|
||||
|
||||
export default createStore({
|
||||
modules: {
|
||||
@@ -24,6 +23,5 @@ export default createStore({
|
||||
globalConfig,
|
||||
message,
|
||||
campaign,
|
||||
article,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { getMostReadArticles } from 'widget/api/article';
|
||||
|
||||
const state = {
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isError: false,
|
||||
hasFetched: false,
|
||||
isFetching: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
uiFlags: $state => $state.uiFlags,
|
||||
popularArticles: $state => $state.records,
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
fetch: async ({ commit }, { slug, locale }) => {
|
||||
commit('setIsFetching', true);
|
||||
commit('setError', false);
|
||||
|
||||
try {
|
||||
const { data } = await getMostReadArticles(slug, locale);
|
||||
const { payload = [] } = data;
|
||||
|
||||
if (payload.length) {
|
||||
commit('setArticles', payload);
|
||||
}
|
||||
} catch (error) {
|
||||
commit('setError', true);
|
||||
} finally {
|
||||
commit('setIsFetching', false);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const mutations = {
|
||||
setArticles($state, data) {
|
||||
$state.records = data;
|
||||
},
|
||||
setError($state, value) {
|
||||
$state.uiFlags.isError = value;
|
||||
},
|
||||
setIsFetching($state, value) {
|
||||
$state.uiFlags.isFetching = value;
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { API } from 'widget/helpers/axios';
|
||||
|
||||
export default {
|
||||
index: params => {
|
||||
const url = `/hc/${params.slug}/${params.locale}/articles.json`;
|
||||
const urlParams = {
|
||||
page: 1,
|
||||
sort: 'views',
|
||||
status: 1,
|
||||
};
|
||||
return API.get(url, { params: urlParams });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import articleAPI from './api/articleAPI';
|
||||
import { createResourceStore } from './piniaStoreFactory';
|
||||
|
||||
export const useArticleStore = createResourceStore('articles', {
|
||||
api: articleAPI,
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const createResourceStore = (
|
||||
storeName,
|
||||
{ api, customGetters = {}, customActions = {} }
|
||||
) => {
|
||||
return defineStore(storeName, {
|
||||
state: () => ({
|
||||
records: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isCreating: false,
|
||||
isUpdating: false,
|
||||
isDeleting: false,
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getRecords: state => state.records,
|
||||
getUIFlags: state => state.uiFlags,
|
||||
getError: state => state.error,
|
||||
...customGetters,
|
||||
},
|
||||
|
||||
actions: {
|
||||
async index(params = {}) {
|
||||
this.uiFlags.isFetching = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { payload = [] },
|
||||
} = await api.index(params);
|
||||
this.records = [...payload];
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
throw error;
|
||||
} finally {
|
||||
this.uiFlags.isFetching = false;
|
||||
}
|
||||
},
|
||||
|
||||
async create(payload) {
|
||||
this.uiFlags.isCreating = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const { data } = await api.post(payload);
|
||||
this.records.push(data.payload);
|
||||
return data.payload;
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
throw error;
|
||||
} finally {
|
||||
this.uiFlags.isCreating = false;
|
||||
}
|
||||
},
|
||||
|
||||
async update(id, payload) {
|
||||
this.uiFlags.isUpdating = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const { data } = await api.put(id, payload);
|
||||
const index = this.records.findIndex(record => record.id === id);
|
||||
if (index !== -1) {
|
||||
this.records[index] = data.payload;
|
||||
}
|
||||
return data.payload;
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
throw error;
|
||||
} finally {
|
||||
this.uiFlags.isUpdating = false;
|
||||
}
|
||||
},
|
||||
|
||||
async delete(id) {
|
||||
this.uiFlags.isDeleting = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
await api.delete(id);
|
||||
this.records = this.records.filter(record => record.id !== id);
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
throw error;
|
||||
} finally {
|
||||
this.uiFlags.isDeleting = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.records = [];
|
||||
this.error = null;
|
||||
Object.keys(this.uiFlags).forEach(key => {
|
||||
this.uiFlags[key] = false;
|
||||
});
|
||||
},
|
||||
|
||||
...customActions,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,62 +1,26 @@
|
||||
<script>
|
||||
import TeamAvailability from 'widget/components/TeamAvailability.vue';
|
||||
import ArticleHero from 'widget/components/pageComponents/Home/Article/ArticleBlock.vue';
|
||||
import ArticleCardSkeletonLoader from 'widget/components/pageComponents/Home/Article/SkeletonLoader.vue';
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import ArticleContainer from '../components/pageComponents/Home/Article/ArticleContainer.vue';
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
ArticleHero,
|
||||
ArticleContainer,
|
||||
TeamAvailability,
|
||||
ArticleCardSkeletonLoader,
|
||||
},
|
||||
mixins: [configMixin, routerMixin],
|
||||
setup() {
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
return { prefersDarkMode };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
availableAgents: 'agent/availableAgents',
|
||||
conversationSize: 'conversation/getConversationSize',
|
||||
unreadMessageCount: 'conversation/getUnreadMessageCount',
|
||||
popularArticles: 'article/popularArticles',
|
||||
articleUiFlags: 'article/uiFlags',
|
||||
}),
|
||||
widgetLocale() {
|
||||
return this.$i18n.locale || 'en';
|
||||
},
|
||||
portal() {
|
||||
return window.chatwootWebChannel.portal;
|
||||
},
|
||||
defaultLocale() {
|
||||
const widgetLocale = this.widgetLocale;
|
||||
const { allowed_locales: allowedLocales, default_locale: defaultLocale } =
|
||||
this.portal.config;
|
||||
},
|
||||
|
||||
// IMPORTANT: Variation strict locale matching, Follow iso_639_1_code
|
||||
// If the exact match of a locale is available in the list of portal locales, return it
|
||||
// Else return the default locale. Eg: `es` will not work if `es_ES` is available in the list
|
||||
if (allowedLocales.includes(widgetLocale)) {
|
||||
return widgetLocale;
|
||||
}
|
||||
return defaultLocale;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.portal && this.popularArticles.length === 0) {
|
||||
const locale = this.defaultLocale;
|
||||
this.$store.dispatch('article/fetch', {
|
||||
slug: this.portal.slug,
|
||||
locale,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startConversation() {
|
||||
if (this.preChatFormEnabled && !this.conversationSize) {
|
||||
@@ -64,24 +28,6 @@ export default {
|
||||
}
|
||||
return this.replaceRoute('messages');
|
||||
},
|
||||
openArticleInArticleViewer(link) {
|
||||
let linkToOpen = `${link}?show_plain_layout=true`;
|
||||
const isDark = this.prefersDarkMode;
|
||||
if (isDark) {
|
||||
linkToOpen = `${linkToOpen}&theme=dark`;
|
||||
}
|
||||
this.$router.push({
|
||||
name: 'article-viewer',
|
||||
query: { link: linkToOpen },
|
||||
});
|
||||
},
|
||||
viewAllArticles() {
|
||||
const locale = this.defaultLocale;
|
||||
const {
|
||||
portal: { slug },
|
||||
} = window.chatwootWebChannel;
|
||||
this.openArticleInArticleViewer(`/hc/${slug}/${locale}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -95,21 +41,6 @@ export default {
|
||||
@start-conversation="startConversation"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="portal"
|
||||
class="w-full shadow outline-1 outline outline-n-container rounded-xl bg-n-solid-2 px-5 py-4"
|
||||
>
|
||||
<ArticleHero
|
||||
v-if="
|
||||
!articleUiFlags.isFetching &&
|
||||
!articleUiFlags.isError &&
|
||||
popularArticles.length
|
||||
"
|
||||
:articles="popularArticles"
|
||||
@view="openArticleInArticleViewer"
|
||||
@view-all="viewAllArticles"
|
||||
/>
|
||||
<ArticleCardSkeletonLoader v-else />
|
||||
</div>
|
||||
<ArticleContainer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user