Merge branch 'develop' into vue3-migration
This commit is contained in:
commit
e831daf2b4
30 changed files with 767 additions and 95 deletions
|
@ -39,7 +39,7 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
||||||
# TODO: move this to a builder and combine the save account user method into a builder
|
# TODO: move this to a builder and combine the save account user method into a builder
|
||||||
# ensure the account user association is also created in a single transaction
|
# ensure the account user association is also created in a single transaction
|
||||||
def create_user
|
def create_user
|
||||||
return if @user
|
return @user.send_confirmation_instructions if @user
|
||||||
|
|
||||||
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
|
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,16 @@ class ArticlesAPI extends PortalsAPI {
|
||||||
articleObj
|
articleObj
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createArticle({ portalSlug, articleObj }) {
|
||||||
|
const { content, title, author_id, category_id } = articleObj;
|
||||||
|
return axios.post(`${this.url}/${portalSlug}/articles`, {
|
||||||
|
content,
|
||||||
|
title,
|
||||||
|
author_id,
|
||||||
|
category_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ArticlesAPI();
|
export default new ArticlesAPI();
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
/* global axios */
|
||||||
import ApiClient from '../ApiClient';
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
class PortalsAPI extends ApiClient {
|
class PortalsAPI extends ApiClient {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('portals', { accountScoped: true });
|
super('portals', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePortal({ portalSlug, params }) {
|
||||||
|
return axios.patch(`${this.url}/${portalSlug}`, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PortalsAPI;
|
export default PortalsAPI;
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.border-right {
|
.border-right {
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
@ -66,3 +65,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-end {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-align-center {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
|
@ -91,10 +91,11 @@
|
||||||
font-size: $font-size-default;
|
font-size: $font-size-default;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding-left: $space-medium;
|
padding-left: $space-medium;
|
||||||
|
}
|
||||||
|
|
||||||
.completed {
|
.completed {
|
||||||
color: $success-color;
|
color: $success-color;
|
||||||
}
|
margin-left: $space-smaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<transition-group
|
<transition-group
|
||||||
name="wizard-items"
|
name="wizard-items"
|
||||||
tag="div"
|
tag="div"
|
||||||
class="wizard-box flex-child-shrink"
|
class="wizard-box"
|
||||||
:class="classObject"
|
:class="classObject"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -11,12 +11,14 @@
|
||||||
class="item"
|
class="item"
|
||||||
:class="{ active: isActive(item), over: isOver(item) }"
|
:class="{ active: isActive(item), over: isOver(item) }"
|
||||||
>
|
>
|
||||||
<h3>
|
<div class="flex-align-center">
|
||||||
{{ item.title }}
|
<h3 class="text-truncate">
|
||||||
|
{{ item.title }}
|
||||||
|
</h3>
|
||||||
<span v-if="isOver(item)" class="completed">
|
<span v-if="isOver(item)" class="completed">
|
||||||
<fluent-icon icon="checkmark" />
|
<fluent-icon icon="checkmark" />
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</div>
|
||||||
<span class="step">
|
<span class="step">
|
||||||
{{ items.indexOf(item) + 1 }}
|
{{ items.indexOf(item) + 1 }}
|
||||||
</span>
|
</span>
|
||||||
|
|
8
app/javascript/dashboard/helper/labelColor.js
Normal file
8
app/javascript/dashboard/helper/labelColor.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export const getRandomColor = () => {
|
||||||
|
const letters = '0123456789ABCDEF';
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i < 6; i += 1) {
|
||||||
|
color += letters[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
|
@ -112,20 +112,55 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ADD": {
|
"ADD": {
|
||||||
"TITLE": "Create a portal",
|
"CREATE_FLOW": [
|
||||||
"SUB_TITLE": "A Help Center in Chatwoot is known as a portal. You can have multiple portals and can have different locales for each portal.",
|
{
|
||||||
|
"title": "Help center information",
|
||||||
|
"route": "new_portal_information",
|
||||||
|
"body": "Basic information about portal",
|
||||||
|
"CREATE_BASIC_SETTING_BUTTON": "Create portal basic settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Help center customization",
|
||||||
|
"route": "portal_customization",
|
||||||
|
"body": "Customize portal",
|
||||||
|
"UPDATE_PORTAL_BUTTON": "Update portal settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Voila! 🎉",
|
||||||
|
"route": "portal_finish",
|
||||||
|
"body": "You're all set!",
|
||||||
|
"FINISH": "Finish"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CREATE_FLOW_PAGE": {
|
||||||
|
"BACK_BUTTON": "Back",
|
||||||
|
"BASIC_SETTINGS_PAGE": {
|
||||||
|
"HEADER": "Create Portal",
|
||||||
|
"TITLE": "Help center information",
|
||||||
|
"CREATE_BASIC_SETTING_BUTTON": "Create portal basic settings"
|
||||||
|
},
|
||||||
|
"CUSTOMIZATION_PAGE": {
|
||||||
|
"HEADER": "Portal customisation",
|
||||||
|
"TITLE": "Help center customization",
|
||||||
|
"UPDATE_PORTAL_BUTTON": "Update portal settings"
|
||||||
|
},
|
||||||
|
"FINISH_PAGE": {
|
||||||
|
"TITLE": "Voila!🎉 You're all set up!",
|
||||||
|
"MESSAGE": "You can now see this created portal on your all portals page.",
|
||||||
|
"FINISH": "Go to all portals page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LOGO": {
|
||||||
|
"LABEL": "Logo",
|
||||||
|
"UPLOAD_BUTTON": "Upload logo",
|
||||||
|
"HELP_TEXT": "This logo will be displayed on the portal header."
|
||||||
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "Name",
|
"LABEL": "Name",
|
||||||
"PLACEHOLDER": "Portal name",
|
"PLACEHOLDER": "Portal name",
|
||||||
"HELP_TEXT": "The name will be used in the public facing portal internally",
|
"HELP_TEXT": "The name will be used in the public facing portal internally.",
|
||||||
"ERROR": "Name is required"
|
"ERROR": "Name is required"
|
||||||
},
|
},
|
||||||
"PAGE_TITLE": {
|
|
||||||
"LABEL": "Page Title",
|
|
||||||
"PLACEHOLDER": "Portal page title",
|
|
||||||
"HELP_TEXT": "The name will be used in the public facing portal",
|
|
||||||
"ERROR": "Page title is required"
|
|
||||||
},
|
|
||||||
"SLUG": {
|
"SLUG": {
|
||||||
"LABEL": "Slug",
|
"LABEL": "Slug",
|
||||||
"PLACEHOLDER": "Portal slug for urls",
|
"PLACEHOLDER": "Portal slug for urls",
|
||||||
|
@ -135,7 +170,7 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Custom Domain is required"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
|
@ -144,19 +179,25 @@
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Home Page Link is required"
|
||||||
},
|
},
|
||||||
|
"THEME_COLOR": {
|
||||||
|
"LABEL": "Portal theme color",
|
||||||
|
"HELP_TEXT": "This color will show as the theme color for the portal."
|
||||||
|
},
|
||||||
|
"PAGE_TITLE": {
|
||||||
|
"LABEL": "Page Title",
|
||||||
|
"PLACEHOLDER": "Portal page title",
|
||||||
|
"HELP_TEXT": "The page title will be used in the public facing portal.",
|
||||||
|
"ERROR": "Page title is required"
|
||||||
|
},
|
||||||
"HEADER_TEXT": {
|
"HEADER_TEXT": {
|
||||||
"LABEL": "Header Text",
|
"LABEL": "Header Text",
|
||||||
"PLACEHOLDER": "Portal header text",
|
"PLACEHOLDER": "Portal header text",
|
||||||
"HELP_TEXT": "Portal header text",
|
"HELP_TEXT": "The Portal header text will be used in the public facing portal.",
|
||||||
"ERROR": "Portal header text is required"
|
"ERROR": "Portal header text is required"
|
||||||
},
|
},
|
||||||
"BUTTONS": {
|
|
||||||
"CREATE": "Create portal",
|
|
||||||
"CANCEL": "Cancel"
|
|
||||||
},
|
|
||||||
"API": {
|
"API": {
|
||||||
"SUCCESS_MESSAGE": "Portal created successfully.",
|
"ERROR_MESSAGE_FOR_BASIC": "Couldn't create the portal. Try again.",
|
||||||
"ERROR_MESSAGE": "Couldn't create the portal. Try again."
|
"ERROR_MESSAGE_FOR_UPDATE": "Couldn't update the portal. Try again."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -183,6 +224,9 @@
|
||||||
"ERROR": "Error while saving article"
|
"ERROR": "Error while saving article"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CREATE_ARTICLE": {
|
||||||
|
"ERROR_MESSAGE": "Something went wrong. Please try again."
|
||||||
|
},
|
||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"PLACEHOLDER": "Search for articles"
|
"PLACEHOLDER": "Search for articles"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
@open-key-shortcut-modal="toggleKeyShortcutModal"
|
@open-key-shortcut-modal="toggleKeyShortcutModal"
|
||||||
@close-key-shortcut-modal="closeKeyShortcutModal"
|
@close-key-shortcut-modal="closeKeyShortcutModal"
|
||||||
/>
|
/>
|
||||||
<div v-if="portals.length" class="margin-right-small">
|
<div v-if="portals.length">
|
||||||
<help-center-sidebar
|
<help-center-sidebar
|
||||||
:class="sidebarClassName"
|
:class="sidebarClassName"
|
||||||
:header-title="headerTitle"
|
:header-title="headerTitle"
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
'HELP_CENTER.PORTAL.PORTAL_SETTINGS.LIST_ITEM.PORTAL_CONFIG.ITEMS.NAME'
|
'HELP_CENTER.PORTAL.PORTAL_SETTINGS.LIST_ITEM.PORTAL_CONFIG.ITEMS.NAME'
|
||||||
)
|
)
|
||||||
}}</label>
|
}}</label>
|
||||||
<span class="text-block-title">{{ portal.header_text }}</span>
|
<span class="text-block-title">{{ portal.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="configuration-item">
|
<div class="configuration-item">
|
||||||
<label>{{
|
<label>{{
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
icon="settings"
|
icon="settings"
|
||||||
size="small"
|
size="small"
|
||||||
|
@click="openPortalPage"
|
||||||
>
|
>
|
||||||
{{ $t('HELP_CENTER.PORTAL.POPOVER.PORTAL_SETTINGS') }}
|
{{ $t('HELP_CENTER.PORTAL.POPOVER.PORTAL_SETTINGS') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
|
@ -60,16 +61,10 @@ export default {
|
||||||
closePortalPopover() {
|
closePortalPopover() {
|
||||||
this.$emit('close-popover');
|
this.$emit('close-popover');
|
||||||
},
|
},
|
||||||
openPortalPage({ slug, locale }) {
|
openPortalPage() {
|
||||||
this.$emit('close-popover');
|
this.$emit('close-popover');
|
||||||
const portal = this.portals.find(p => p.slug === slug);
|
|
||||||
this.$store.dispatch('portals/setPortalId', portal.id);
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'list_all_locale_articles',
|
name: 'list_all_portals',
|
||||||
params: {
|
|
||||||
portalSlug: slug,
|
|
||||||
locale: locale,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -86,6 +81,7 @@ export default {
|
||||||
border-radius: var(--border-radius-normal);
|
border-radius: var(--border-radius-normal);
|
||||||
box-shadow: var(--shadow-large);
|
box-shadow: var(--shadow-large);
|
||||||
max-width: 48rem;
|
max-width: 48rem;
|
||||||
|
z-index: var(--z-index-high);
|
||||||
|
|
||||||
header {
|
header {
|
||||||
.actions {
|
.actions {
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
variant="square"
|
variant="square"
|
||||||
/>
|
/>
|
||||||
<div class="header-title--wrap">
|
<div class="header-title--wrap">
|
||||||
<h4 class="sub-block-title title-view">{{ headerTitle }}</h4>
|
<h4 class="sub-block-title title-view text-truncate">
|
||||||
|
{{ headerTitle }}
|
||||||
|
</h4>
|
||||||
<span class="sub-title--view">{{ subTitle }}</span>
|
<span class="sub-title--view">{{ subTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,6 +91,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-view {
|
.title-view {
|
||||||
|
width: var(--space-mega);
|
||||||
margin-bottom: var(--space-zero);
|
margin-bottom: var(--space-zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,19 @@ import { getPortalRoute } from './helpers/routeHelper';
|
||||||
|
|
||||||
const ListAllPortals = () => import('./pages/portals/ListAllPortals');
|
const ListAllPortals = () => import('./pages/portals/ListAllPortals');
|
||||||
const NewPortal = () => import('./pages/portals/NewPortal');
|
const NewPortal = () => import('./pages/portals/NewPortal');
|
||||||
|
|
||||||
const EditPortal = () => import('./pages/portals/EditPortal');
|
const EditPortal = () => import('./pages/portals/EditPortal');
|
||||||
const ShowPortal = () => import('./pages/portals/ShowPortal');
|
const ShowPortal = () => import('./pages/portals/ShowPortal');
|
||||||
|
const PortalDetails = () => import('./pages/portals/PortalDetails');
|
||||||
|
const PortalCustomization = () => import('./pages/portals/PortalCustomization');
|
||||||
|
const PortalSettingsFinish = () =>
|
||||||
|
import('./pages/portals/PortalSettingsFinish');
|
||||||
const ListAllCategories = () => import('./pages/categories/ListAllCategories');
|
const ListAllCategories = () => import('./pages/categories/ListAllCategories');
|
||||||
const NewCategory = () => import('./pages/categories/NewCategory');
|
const NewCategory = () => import('./pages/categories/NewCategory');
|
||||||
const EditCategory = () => import('./pages/categories/EditCategory');
|
const EditCategory = () => import('./pages/categories/EditCategory');
|
||||||
// const ShowCategory = () => import('./pages/categories/ShowCategory');
|
|
||||||
const ListCategoryArticles = () =>
|
const ListCategoryArticles = () =>
|
||||||
import('./pages/articles/ListCategoryArticles');
|
import('./pages/articles/ListCategoryArticles');
|
||||||
|
|
||||||
const ListAllArticles = () => import('./pages/articles/ListAllArticles');
|
const ListAllArticles = () => import('./pages/articles/ListAllArticles');
|
||||||
|
|
||||||
const NewArticle = () => import('./pages/articles/NewArticle');
|
const NewArticle = () => import('./pages/articles/NewArticle');
|
||||||
const EditArticle = () => import('./pages/articles/EditArticle');
|
const EditArticle = () => import('./pages/articles/EditArticle');
|
||||||
|
|
||||||
|
@ -27,9 +28,27 @@ const portalRoutes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: getPortalRoute('new'),
|
path: getPortalRoute('new'),
|
||||||
name: 'new_portal',
|
|
||||||
roles: ['administrator', 'agent'],
|
|
||||||
component: NewPortal,
|
component: NewPortal,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'new_portal_information',
|
||||||
|
component: PortalDetails,
|
||||||
|
roles: ['administrator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':portal_slug/customization',
|
||||||
|
name: 'portal_customization',
|
||||||
|
component: PortalCustomization,
|
||||||
|
roles: ['administrator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':portal_slug/finish',
|
||||||
|
name: 'portal_finish',
|
||||||
|
component: PortalSettingsFinish,
|
||||||
|
roles: ['administrator'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: getPortalRoute(':portalSlug'),
|
path: getPortalRoute(':portalSlug'),
|
||||||
|
|
|
@ -51,12 +51,14 @@ export default {
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
isSaved: false,
|
isSaved: false,
|
||||||
showArticleSettings: false,
|
showArticleSettings: false,
|
||||||
|
alertMessage: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
isFetching: 'articles/isFetching',
|
isFetching: 'articles/isFetching',
|
||||||
articles: 'articles/articles',
|
articles: 'articles/articles',
|
||||||
|
selectedPortal: 'portals/getSelectedPortal',
|
||||||
}),
|
}),
|
||||||
article() {
|
article() {
|
||||||
return this.$store.getters['articles/articleById'](this.articleId);
|
return this.$store.getters['articles/articleById'](this.articleId);
|
||||||
|
@ -93,6 +95,7 @@ export default {
|
||||||
this.alertMessage =
|
this.alertMessage =
|
||||||
error?.message ||
|
error?.message ||
|
||||||
this.$t('HELP_CENTER.EDIT_ARTICLE.API.ERROR_MESSAGE');
|
this.$t('HELP_CENTER.EDIT_ARTICLE.API.ERROR_MESSAGE');
|
||||||
|
this.showAlert(this.alertMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<div class="article-container">
|
||||||
<edit-article-header
|
<edit-article-header
|
||||||
back-button-label="All Articles"
|
:back-button-label="$t('HELP_CENTER.HEADER.TITLES.ALL_ARTICLES')"
|
||||||
draft-state="saved"
|
draft-state="saved"
|
||||||
@back="onClickGoBack"
|
@back="onClickGoBack"
|
||||||
|
@save-article="createNewArticle"
|
||||||
/>
|
/>
|
||||||
<article-editor @titleInput="titleInput" @contentInput="contentInput" />
|
<article-editor :article="article" @save-article="createNewArticle" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader';
|
import EditArticleHeader from 'dashboard/routes/dashboard/helpcenter/components/Header/EditArticleHeader';
|
||||||
import ArticleEditor from '../../components/ArticleEditor.vue';
|
import ArticleEditor from '../../components/ArticleEditor.vue';
|
||||||
|
import portalMixin from '../../mixins/portalMixin';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin.js';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
EditArticleHeader,
|
EditArticleHeader,
|
||||||
ArticleEditor,
|
ArticleEditor,
|
||||||
},
|
},
|
||||||
|
mixins: [portalMixin, alertMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
articleTitle: '',
|
articleTitle: '',
|
||||||
|
@ -24,21 +29,55 @@ export default {
|
||||||
showArticleSettings: false,
|
showArticleSettings: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedPortal: 'portals/getSelectedPortal',
|
||||||
|
currentUserID: 'getCurrentUserID',
|
||||||
|
categories: 'categories/allCategories',
|
||||||
|
}),
|
||||||
|
article() {
|
||||||
|
return { title: this.articleTitle, content: this.articleContent };
|
||||||
|
},
|
||||||
|
selectedPortalSlug() {
|
||||||
|
return this.portalSlug || this.selectedPortal?.slug;
|
||||||
|
},
|
||||||
|
categoryId() {
|
||||||
|
return this.categories.length ? this.categories[0].id : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClickGoBack() {
|
onClickGoBack() {
|
||||||
this.$router.push({ name: 'list_all_locale_articles' });
|
this.$router.push({ name: 'list_all_locale_articles' });
|
||||||
},
|
},
|
||||||
titleInput(value) {
|
async createNewArticle({ ...values }) {
|
||||||
this.articleTitle = value;
|
const { title, content } = values;
|
||||||
},
|
if (title) this.articleTitle = title;
|
||||||
contentInput(value) {
|
if (content) this.articleContent = content;
|
||||||
this.articleContent = value;
|
if (this.articleTitle && this.articleContent) {
|
||||||
},
|
try {
|
||||||
openArticleSettings() {
|
const articleId = await this.$store.dispatch('articles/create', {
|
||||||
this.showArticleSettings = true;
|
portalSlug: this.selectedPortalSlug,
|
||||||
},
|
content: this.articleContent,
|
||||||
closeArticleSettings() {
|
title: this.articleTitle,
|
||||||
this.showArticleSettings = false;
|
author_id: this.currentUserID,
|
||||||
|
// TODO: Change to un categorized later when API supports
|
||||||
|
category_id: this.categoryId,
|
||||||
|
});
|
||||||
|
this.$router.push({
|
||||||
|
name: 'edit_article',
|
||||||
|
params: {
|
||||||
|
articleSlug: articleId,
|
||||||
|
portalSlug: this.selectedPortalSlug,
|
||||||
|
locale: this.locale,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.alertMessage =
|
||||||
|
error?.message ||
|
||||||
|
this.$t('HELP_CENTER.CREATE_ARTICLE.API.ERROR_MESSAGE');
|
||||||
|
this.showAlert(this.alertMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -46,7 +85,6 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.article-container {
|
.article-container {
|
||||||
display: flex;
|
|
||||||
padding: var(--space-small) var(--space-normal);
|
padding: var(--space-small) var(--space-normal);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addPortal() {
|
addPortal() {
|
||||||
this.isAddModalOpen = !this.isAddModalOpen;
|
this.$router.push({ name: 'new_portal_information' });
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.isAddModalOpen = false;
|
this.isAddModalOpen = false;
|
||||||
|
|
|
@ -1,5 +1,79 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="wrapper">
|
||||||
Component to create a new portal
|
<settings-header
|
||||||
|
button-route="new"
|
||||||
|
:header-title="portalHeaderText"
|
||||||
|
show-back-button
|
||||||
|
:back-button-label="
|
||||||
|
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BACK_BUTTON')
|
||||||
|
"
|
||||||
|
:show-new-button="false"
|
||||||
|
/>
|
||||||
|
<div class="row content-box full-height">
|
||||||
|
<woot-wizard
|
||||||
|
class="hide-for-small-only medium-3 columns"
|
||||||
|
:global-config="globalConfig"
|
||||||
|
:items="items"
|
||||||
|
/>
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
|
import SettingsHeader from 'dashboard/routes/dashboard/settings/SettingsHeader';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SettingsHeader,
|
||||||
|
},
|
||||||
|
mixins: [globalConfigMixin],
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
globalConfig: 'globalConfig/get',
|
||||||
|
}),
|
||||||
|
items() {
|
||||||
|
const allItems = this.$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW').map(
|
||||||
|
item => ({
|
||||||
|
...item,
|
||||||
|
body: this.useInstallationName(
|
||||||
|
item.body,
|
||||||
|
this.globalConfig.installationName
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return allItems;
|
||||||
|
},
|
||||||
|
portalHeaderText() {
|
||||||
|
if (this.$route.name === 'new_portal_information') {
|
||||||
|
return this.$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.HEADER'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.$route.name === 'portal_customization') {
|
||||||
|
return this.$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.HEADER'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.wizard-box {
|
||||||
|
border-right: 1px solid var(--s-25);
|
||||||
|
::v-deep .item {
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
<template>
|
||||||
|
<div class="wizard-body height-auto small-9 columns">
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<h3 class="block-title">
|
||||||
|
{{
|
||||||
|
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.TITLE')
|
||||||
|
}}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="portal-form">
|
||||||
|
<div class="medium-8 columns">
|
||||||
|
<div class="form-item">
|
||||||
|
<label>
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.LABEL') }}
|
||||||
|
</label>
|
||||||
|
<woot-color-picker v-model="color" />
|
||||||
|
<p class="color-help--text">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.THEME_COLOR.HELP_TEXT') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="pageTitle"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.PAGE_TITLE.HELP_TEXT')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="headerText"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.HEADER_TEXT.HELP_TEXT')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="homePageLink"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.LABEL')"
|
||||||
|
:placeholder="
|
||||||
|
$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.HOME_PAGE_LINK.HELP_TEXT')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-end">
|
||||||
|
<woot-button
|
||||||
|
:is-loading="uiFlags.isUpdating"
|
||||||
|
@click="updatePortalSettings"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.CUSTOMIZATION_PAGE.UPDATE_PORTAL_BUTTON'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||||
|
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
mixins: [alertMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
color: '#000',
|
||||||
|
pageTitle: '',
|
||||||
|
headerText: '',
|
||||||
|
homePageLink: '',
|
||||||
|
alertMessage: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
uiFlags: 'portals/uiFlagsIn',
|
||||||
|
portals: 'portals/allPortals',
|
||||||
|
}),
|
||||||
|
createdPortalSlug() {
|
||||||
|
const {
|
||||||
|
params: { portal_slug: slug },
|
||||||
|
} = this.$route;
|
||||||
|
return slug;
|
||||||
|
},
|
||||||
|
createdPortalId() {
|
||||||
|
const { portals } = this;
|
||||||
|
const createdPortal = portals.find(
|
||||||
|
portal => portal.slug === this.createdPortalSlug
|
||||||
|
);
|
||||||
|
return createdPortal ? createdPortal.id : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchPortals();
|
||||||
|
this.color = getRandomColor();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchPortals() {
|
||||||
|
this.$store.dispatch('portals/index');
|
||||||
|
},
|
||||||
|
async updatePortalSettings() {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('portals/update', {
|
||||||
|
id: this.createdPortalId,
|
||||||
|
slug: this.createdPortalSlug,
|
||||||
|
color: this.color,
|
||||||
|
page_title: this.pageTitle,
|
||||||
|
header_text: this.headerText,
|
||||||
|
homepage_link: this.homePageLink,
|
||||||
|
config: {
|
||||||
|
// TODO: add support for choosing locale
|
||||||
|
allowed_locales: ['en'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.alertMessage = this.$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_UPDATE'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.alertMessage =
|
||||||
|
error?.message ||
|
||||||
|
this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_UPDATE');
|
||||||
|
} finally {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'portal_finish',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wizard-body {
|
||||||
|
padding-top: var(--space-slab);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.portal-form {
|
||||||
|
margin: var(--space-normal) 0;
|
||||||
|
border-bottom: 1px solid var(--s-25);
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: var(--space-normal);
|
||||||
|
.color-help--text {
|
||||||
|
margin-top: var(--space-smaller);
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
color: var(--s-600);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep {
|
||||||
|
input {
|
||||||
|
margin-bottom: var(--space-smaller);
|
||||||
|
}
|
||||||
|
.help-text {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.colorpicker--selected {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,203 @@
|
||||||
|
<template>
|
||||||
|
<div class="wizard-body columns content-box small-9">
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<h3 class="block-title">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.TITLE'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="portal-form">
|
||||||
|
<div class="medium-8 columns">
|
||||||
|
<div class="form-item">
|
||||||
|
<label>
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.LABEL') }}
|
||||||
|
</label>
|
||||||
|
<div class="logo-container">
|
||||||
|
<thumbnail :username="name" size="56" variant="square" />
|
||||||
|
<woot-button
|
||||||
|
class="upload-button"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
icon="upload"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.UPLOAD_BUTTON') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
<p class="logo-help--text">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.HELP_TEXT') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="name"
|
||||||
|
:class="{ error: $v.slug.$error }"
|
||||||
|
:error="nameError"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.NAME.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.PORTAL.ADD.NAME.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.NAME.HELP_TEXT')"
|
||||||
|
@input="onNameChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="slug"
|
||||||
|
:class="{ error: $v.slug.$error }"
|
||||||
|
:error="slugError"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.SLUG.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.PORTAL.ADD.SLUG.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.SLUG.HELP_TEXT')"
|
||||||
|
@input="$v.slug.$touch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="domain"
|
||||||
|
:label="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.PORTAL.ADD.DOMAIN.HELP_TEXT')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-end">
|
||||||
|
<woot-button
|
||||||
|
:is-loading="uiFlags.isCreating"
|
||||||
|
:is-disabled="$v.$invalid"
|
||||||
|
@click="updateBasicSettings"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.BASIC_SETTINGS_PAGE.CREATE_BASIC_SETTING_BUTTON'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import { required, minLength } from 'vuelidate/lib/validators';
|
||||||
|
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
thumbnail,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
slug: '',
|
||||||
|
domain: '',
|
||||||
|
alertMessage: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
name: {
|
||||||
|
required,
|
||||||
|
minLength: minLength(2),
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
uiFlags: 'portals/uiFlagsIn',
|
||||||
|
}),
|
||||||
|
nameError() {
|
||||||
|
if (this.$v.name.$error) {
|
||||||
|
return this.$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
slugError() {
|
||||||
|
if (this.$v.slug.$error) {
|
||||||
|
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
domainError() {
|
||||||
|
return this.$v.domain.$error;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onNameChange() {
|
||||||
|
this.slug = convertToCategorySlug(this.name);
|
||||||
|
},
|
||||||
|
async updateBasicSettings() {
|
||||||
|
this.$v.$touch();
|
||||||
|
if (this.$v.$invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('portals/create', {
|
||||||
|
portal: {
|
||||||
|
name: this.name,
|
||||||
|
slug: this.slug,
|
||||||
|
custom_domain: this.domain,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.alertMessage = this.$t(
|
||||||
|
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE_FOR_BASIC'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.alertMessage =
|
||||||
|
error?.message ||
|
||||||
|
this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE_FOR_BASIC');
|
||||||
|
} finally {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'portal_customization',
|
||||||
|
params: { portal_slug: this.slug },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wizard-body {
|
||||||
|
padding-top: var(--space-slab);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-form {
|
||||||
|
margin: var(--space-normal) 0;
|
||||||
|
border-bottom: 1px solid var(--s-25);
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: var(--space-normal);
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
.upload-button {
|
||||||
|
margin-left: var(--space-slab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.logo-help--text {
|
||||||
|
margin-top: var(--space-smaller);
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
color: var(--s-600);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep {
|
||||||
|
input {
|
||||||
|
margin-bottom: var(--space-smaller);
|
||||||
|
}
|
||||||
|
.help-text {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<div class="wizard-body height-auto small-9 columns">
|
||||||
|
<empty-state
|
||||||
|
:title="$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.FINISH_PAGE.TITLE')"
|
||||||
|
:message="
|
||||||
|
$t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.FINISH_PAGE.MESSAGE')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="medium-12 columns text-center">
|
||||||
|
<router-link
|
||||||
|
class="button success nice"
|
||||||
|
:to="{
|
||||||
|
name: 'list_all_portals',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.ADD.CREATE_FLOW_PAGE.FINISH_PAGE.FINISH') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</empty-state>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import EmptyState from 'dashboard/components/widgets/EmptyState';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EmptyState,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeRoute() {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'list_all_portals',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.wizard-body {
|
||||||
|
padding-top: var(--space-slab);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -61,6 +61,7 @@ import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import validationMixin from './validationMixin';
|
import validationMixin from './validationMixin';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import validations from './validations';
|
import validations from './validations';
|
||||||
|
import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [alertMixin, validationMixin],
|
mixins: [alertMixin, validationMixin],
|
||||||
|
@ -79,20 +80,12 @@ export default {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.color = this.getRandomColor();
|
this.color = getRandomColor();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClose() {
|
onClose() {
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
getRandomColor() {
|
|
||||||
const letters = '0123456789ABCDEF';
|
|
||||||
let color = '#';
|
|
||||||
for (let i = 0; i < 6; i += 1) {
|
|
||||||
color += letters[Math.floor(Math.random() * 16)];
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
},
|
|
||||||
async addLabel() {
|
async addLabel() {
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('labels/create', {
|
await this.$store.dispatch('labels/create', {
|
||||||
|
|
|
@ -32,13 +32,20 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
create: async ({ commit }, params) => {
|
create: async ({ commit, dispatch }, { portalSlug, ...articleObj }) => {
|
||||||
commit(types.SET_UI_FLAG, { isCreating: true });
|
commit(types.SET_UI_FLAG, { isCreating: true });
|
||||||
try {
|
try {
|
||||||
const { data } = await articlesAPI.create(params);
|
const {
|
||||||
const { id: articleId } = data;
|
data: { payload },
|
||||||
commit(types.ADD_ARTICLE, data);
|
} = await articlesAPI.createArticle({
|
||||||
|
portalSlug,
|
||||||
|
articleObj,
|
||||||
|
});
|
||||||
|
const { id: articleId, portal } = payload;
|
||||||
|
commit(types.ADD_ARTICLE, payload);
|
||||||
commit(types.ADD_ARTICLE_ID, articleId);
|
commit(types.ADD_ARTICLE_ID, articleId);
|
||||||
|
commit(types.ADD_ARTICLE_FLAG, articleId);
|
||||||
|
dispatch('portals/updatePortal', portal, { root: true });
|
||||||
return articleId;
|
return articleId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return throwErrorMessage(error);
|
return throwErrorMessage(error);
|
||||||
|
@ -63,7 +70,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
update: async ({ commit }, { portalSlug, articleId, ...articleObj }) => {
|
||||||
commit(types.ADD_ARTICLE_FLAG, {
|
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isUpdating: true,
|
isUpdating: true,
|
||||||
},
|
},
|
||||||
|
@ -85,7 +92,7 @@ export const actions = {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return throwErrorMessage(error);
|
return throwErrorMessage(error);
|
||||||
} finally {
|
} finally {
|
||||||
commit(types.ADD_ARTICLE_FLAG, {
|
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
},
|
},
|
||||||
|
@ -94,7 +101,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
delete: async ({ commit }, articleId) => {
|
delete: async ({ commit }, articleId) => {
|
||||||
commit(types.ADD_ARTICLE_FLAG, {
|
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
},
|
},
|
||||||
|
@ -109,7 +116,7 @@ export const actions = {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return throwErrorMessage(error);
|
return throwErrorMessage(error);
|
||||||
} finally {
|
} finally {
|
||||||
commit(types.ADD_ARTICLE_FLAG, {
|
commit(types.UPDATE_ARTICLE_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const mutations = {
|
||||||
articles.forEach(article => {
|
articles.forEach(article => {
|
||||||
allArticles[article.id] = article;
|
allArticles[article.id] = article;
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.set($state.articles, 'byId', allArticles);
|
Vue.set($state.articles, 'byId', allArticles);
|
||||||
},
|
},
|
||||||
[types.ADD_MANY_ARTICLES_ID]($state, articleIds) {
|
[types.ADD_MANY_ARTICLES_ID]($state, articleIds) {
|
||||||
|
@ -39,10 +40,13 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.ADD_ARTICLE_ID]: ($state, articleId) => {
|
[types.ADD_ARTICLE_ID]: ($state, articleId) => {
|
||||||
|
if ($state.articles.allIds.includes(articleId)) return;
|
||||||
$state.articles.allIds.push(articleId);
|
$state.articles.allIds.push(articleId);
|
||||||
},
|
},
|
||||||
[types.ADD_ARTICLE_FLAG]: ($state, { articleId, uiFlags }) => {
|
[types.UPDATE_ARTICLE_FLAG]: ($state, { articleId, uiFlags }) => {
|
||||||
const flags = $state.articles.uiFlags.byId[articleId];
|
const flags =
|
||||||
|
Object.keys($state.articles.uiFlags.byId).includes(articleId) || {};
|
||||||
|
|
||||||
Vue.set($state.articles.uiFlags.byId, articleId, {
|
Vue.set($state.articles.uiFlags.byId, articleId, {
|
||||||
...{
|
...{
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
@ -53,6 +57,16 @@ export const mutations = {
|
||||||
...uiFlags,
|
...uiFlags,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
[types.ADD_ARTICLE_FLAG]: ($state, { articleId, uiFlags }) => {
|
||||||
|
Vue.set($state.articles.uiFlags.byId, articleId, {
|
||||||
|
...{
|
||||||
|
isFetching: false,
|
||||||
|
isUpdating: false,
|
||||||
|
isDeleting: false,
|
||||||
|
},
|
||||||
|
...uiFlags,
|
||||||
|
});
|
||||||
|
},
|
||||||
[types.UPDATE_ARTICLE]($state, article) {
|
[types.UPDATE_ARTICLE]($state, article) {
|
||||||
const articleId = article.id;
|
const articleId = article.id;
|
||||||
if (!$state.articles.allIds.includes(articleId)) return;
|
if (!$state.articles.allIds.includes(articleId)) return;
|
||||||
|
|
|
@ -9,6 +9,7 @@ const articleList = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const commit = jest.fn();
|
const commit = jest.fn();
|
||||||
|
const dispatch = jest.fn();
|
||||||
global.axios = axios;
|
global.axios = axios;
|
||||||
jest.mock('axios');
|
jest.mock('axios');
|
||||||
|
|
||||||
|
@ -66,15 +67,17 @@ describe('#actions', () => {
|
||||||
|
|
||||||
describe('#create', () => {
|
describe('#create', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
axios.post.mockResolvedValue({ data: articleList[0] });
|
axios.post.mockResolvedValue({ data: { payload: articleList[0] } });
|
||||||
await actions.create({ commit }, articleList[0]);
|
await actions.create({ commit, dispatch }, articleList[0]);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_UI_FLAG, { isCreating: true }],
|
[types.default.SET_UI_FLAG, { isCreating: true }],
|
||||||
[types.default.ADD_ARTICLE, articleList[0]],
|
[types.default.ADD_ARTICLE, articleList[0]],
|
||||||
[types.default.ADD_ARTICLE_ID, 1],
|
[types.default.ADD_ARTICLE_ID, 1],
|
||||||
|
[types.default.ADD_ARTICLE_FLAG, 1],
|
||||||
[types.default.SET_UI_FLAG, { isCreating: false }],
|
[types.default.SET_UI_FLAG, { isCreating: false }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends correct actions if API is error', async () => {
|
it('sends correct actions if API is error', async () => {
|
||||||
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
await expect(actions.create({ commit }, articleList[0])).rejects.toThrow(
|
await expect(actions.create({ commit }, articleList[0])).rejects.toThrow(
|
||||||
|
@ -100,12 +103,12 @@ describe('#actions', () => {
|
||||||
);
|
);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isUpdating: true }, articleId: 1 },
|
{ uiFlags: { isUpdating: true }, articleId: 1 },
|
||||||
],
|
],
|
||||||
[types.default.UPDATE_ARTICLE, articleList[0]],
|
[types.default.UPDATE_ARTICLE, articleList[0]],
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isUpdating: false }, articleId: 1 },
|
{ uiFlags: { isUpdating: false }, articleId: 1 },
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -125,11 +128,11 @@ describe('#actions', () => {
|
||||||
|
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isUpdating: true }, articleId: 1 },
|
{ uiFlags: { isUpdating: true }, articleId: 1 },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isUpdating: false }, articleId: 1 },
|
{ uiFlags: { isUpdating: false }, articleId: 1 },
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -142,13 +145,13 @@ describe('#actions', () => {
|
||||||
await actions.delete({ commit }, articleList[0].id);
|
await actions.delete({ commit }, articleList[0].id);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isDeleting: true }, articleId: 1 },
|
{ uiFlags: { isDeleting: true }, articleId: 1 },
|
||||||
],
|
],
|
||||||
[types.default.REMOVE_ARTICLE, articleList[0].id],
|
[types.default.REMOVE_ARTICLE, articleList[0].id],
|
||||||
[types.default.REMOVE_ARTICLE_ID, articleList[0].id],
|
[types.default.REMOVE_ARTICLE_ID, articleList[0].id],
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isDeleting: false }, articleId: 1 },
|
{ uiFlags: { isDeleting: false }, articleId: 1 },
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -160,11 +163,11 @@ describe('#actions', () => {
|
||||||
).rejects.toThrow(Error);
|
).rejects.toThrow(Error);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isDeleting: true }, articleId: 1 },
|
{ uiFlags: { isDeleting: true }, articleId: 1 },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
types.default.ADD_ARTICLE_FLAG,
|
types.default.UPDATE_ARTICLE_FLAG,
|
||||||
{ uiFlags: { isDeleting: false }, articleId: 1 },
|
{ uiFlags: { isDeleting: false }, articleId: 1 },
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -49,12 +49,13 @@ export const actions = {
|
||||||
|
|
||||||
update: async ({ commit }, params) => {
|
update: async ({ commit }, params) => {
|
||||||
const portalId = params.id;
|
const portalId = params.id;
|
||||||
|
const portalSlug = params.slug;
|
||||||
commit(types.SET_HELP_PORTAL_UI_FLAG, {
|
commit(types.SET_HELP_PORTAL_UI_FLAG, {
|
||||||
uiFlags: { isUpdating: true },
|
uiFlags: { isUpdating: true },
|
||||||
portalId,
|
portalId,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const { data } = await portalAPIs.update(params);
|
const { data } = await portalAPIs.updatePortal({ portalSlug, params });
|
||||||
commit(types.UPDATE_PORTAL_ENTRY, data);
|
commit(types.UPDATE_PORTAL_ENTRY, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throwErrorMessage(error);
|
throwErrorMessage(error);
|
||||||
|
@ -84,8 +85,10 @@ export const actions = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setPortalId: async ({ commit }, portalId) => {
|
setPortalId: async ({ commit }, portalId) => {
|
||||||
commit(types.SET_SELECTED_PORTAL_ID, portalId);
|
commit(types.SET_SELECTED_PORTAL_ID, portalId);
|
||||||
},
|
},
|
||||||
|
updatePortal: async ({ commit }, portal) => {
|
||||||
|
commit(types.UPDATE_PORTAL_ENTRY, portal);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const mutations = {
|
||||||
[types.CLEAR_PORTALS]: $state => {
|
[types.CLEAR_PORTALS]: $state => {
|
||||||
Vue.set($state.portals, 'byId', {});
|
Vue.set($state.portals, 'byId', {});
|
||||||
Vue.set($state.portals, 'allIds', []);
|
Vue.set($state.portals, 'allIds', []);
|
||||||
Vue.set($state.portals, 'uiFlags', {});
|
Vue.set($state.portals, 'uiFlags.byId', {});
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.SET_PORTALS_META]: ($state, data) => {
|
[types.SET_PORTALS_META]: ($state, data) => {
|
||||||
|
|
|
@ -98,7 +98,11 @@ describe('#mutations', () => {
|
||||||
mutations[types.CLEAR_PORTALS](state);
|
mutations[types.CLEAR_PORTALS](state);
|
||||||
expect(state.portals.allIds).toEqual([]);
|
expect(state.portals.allIds).toEqual([]);
|
||||||
expect(state.portals.byId).toEqual({});
|
expect(state.portals.byId).toEqual({});
|
||||||
expect(state.portals.uiFlags).toEqual({});
|
expect(state.portals.uiFlags).toEqual({
|
||||||
|
byId: {
|
||||||
|
'1': { isFetching: true, isUpdating: true, isDeleting: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,7 @@ export default {
|
||||||
ADD_MANY_ARTICLES: 'ADD_MANY_ARTICLES',
|
ADD_MANY_ARTICLES: 'ADD_MANY_ARTICLES',
|
||||||
ADD_MANY_ARTICLES_ID: 'ADD_MANY_ARTICLES_ID',
|
ADD_MANY_ARTICLES_ID: 'ADD_MANY_ARTICLES_ID',
|
||||||
SET_ARTICLES_META: 'SET_ARTICLES_META',
|
SET_ARTICLES_META: 'SET_ARTICLES_META',
|
||||||
|
UPDATE_ARTICLE_FLAG: 'UPDATE_ARTICLE_FLAG',
|
||||||
ADD_ARTICLE_FLAG: 'ADD_ARTICLE_FLAG',
|
ADD_ARTICLE_FLAG: 'ADD_ARTICLE_FLAG',
|
||||||
UPDATE_ARTICLE: 'UPDATE_ARTICLE',
|
UPDATE_ARTICLE: 'UPDATE_ARTICLE',
|
||||||
CLEAR_ARTICLES: 'CLEAR_ARTICLES',
|
CLEAR_ARTICLES: 'CLEAR_ARTICLES',
|
||||||
|
|
|
@ -5,9 +5,15 @@
|
||||||
<p><%= account_user.inviter.name %>, with <%= account_user.account.name %>, has invited you to try out <%= global_config['BRAND_NAME'] || 'Chatwoot' %>! </p>
|
<p><%= account_user.inviter.name %>, with <%= account_user.account.name %>, has invited you to try out <%= global_config['BRAND_NAME'] || 'Chatwoot' %>! </p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if @resource.confirmed? %>
|
||||||
|
<p>You can login to your account through the link below:</p>
|
||||||
|
<% else %>
|
||||||
<p>You can confirm your account email through the link below:</p>
|
<p>You can confirm your account email through the link below:</p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if account_user&.inviter.present? && @resource.unconfirmed_email.blank? %>
|
<% if @resource.confirmed? %>
|
||||||
|
<p><%= link_to 'Login to my account', frontend_url('auth/sign_in') %></p>
|
||||||
|
<% elsif account_user&.inviter.present? && @resource.unconfirmed_email.blank? %>
|
||||||
<p><%= link_to 'Confirm my account', frontend_url('auth/password/edit', reset_password_token: @resource.send(:set_reset_password_token)) %></p>
|
<p><%= link_to 'Confirm my account', frontend_url('auth/password/edit', reset_password_token: @resource.send(:set_reset_password_token)) %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p><%= link_to 'Confirm my account', frontend_url('auth/confirmation', confirmation_token: @token) %></p>
|
<p><%= link_to 'Confirm my account', frontend_url('auth/confirmation', confirmation_token: @token) %></p>
|
||||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe 'Confirmation Instructions', type: :mailer do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
# to verify the token in email
|
# to verify the token in email
|
||||||
|
confirmable_user.update!(confirmed_at: nil)
|
||||||
confirmable_user.send(:generate_confirmation_token)
|
confirmable_user.send(:generate_confirmation_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,5 +62,17 @@ RSpec.describe 'Confirmation Instructions', type: :mailer do
|
||||||
expect(confirmable_user.unconfirmed_email.blank?).to be false
|
expect(confirmable_user.unconfirmed_email.blank?).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user already confirmed' do
|
||||||
|
before do
|
||||||
|
confirmable_user.confirm
|
||||||
|
confirmable_user.account_users.last.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'send instructions with the link to login' do
|
||||||
|
confirmation_mail = Devise::Mailer.confirmation_instructions(confirmable_user.reload, nil, {})
|
||||||
|
expect(confirmation_mail.body).to include('/auth/sign_in')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue