feat: Add the ability add new portal (#5219)

This commit is contained in:
Muhsin Keloth 2022-08-10 09:27:52 +05:30 committed by GitHub
parent 657bd44418
commit 16ad263a3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 278 additions and 8 deletions

View file

@ -18,8 +18,6 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def create
@portal = Current.account.portals.build(portal_params)
render json: { error: @portal.errors.messages }, status: :unprocessable_entity and return unless @portal.valid?
@portal.save!
process_attached_logo
end

View file

@ -75,3 +75,10 @@ export const convertToCategorySlug = text => {
.replace(/[^\w ]+/g, '')
.replace(/ +/g, '-');
};
export const convertToPortalSlug = text => {
return text
.toLowerCase()
.replace(/[^\w ]+/g, '')
.replace(/ +/g, '-');
};

View file

@ -3,6 +3,7 @@ import {
createPendingMessage,
convertToAttributeSlug,
convertToCategorySlug,
convertToPortalSlug,
} from '../commons';
describe('#getTypingUsersText', () => {
@ -104,3 +105,9 @@ describe('convertToCategorySlug', () => {
);
});
});
describe('convertToPortalSlug', () => {
it('should convert to slug', () => {
expect(convertToPortalSlug('Room rental')).toBe('room-rental');
});
});

View file

@ -74,6 +74,54 @@
}
}
}
},
"ADD": {
"TITLE": "Create a portal",
"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.",
"NAME": {
"LABEL": "Name",
"PLACEHOLDER": "Portal name",
"HELP_TEXT": "The name will be used in the public facing portal internally",
"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": {
"LABEL": "Slug",
"PLACEHOLDER": "Portal slug for urls",
"HELP_TEXT": "app.chatwoot.com/portal/my-portal",
"ERROR": "Slug is required"
},
"DOMAIN": {
"LABEL": "Custom Domain",
"PLACEHOLDER": "Portal custom domain",
"HELP_TEXT": "Add only If you want to use a custom domain for your portals",
"ERROR": "Custom Domain is required"
},
"HOME_PAGE_LINK": {
"LABEL": "Home Page Link",
"PLACEHOLDER": "Portal home page link",
"HELP_TEXT": "The link used to return from the portal to the home page.",
"ERROR": "Home Page Link is required"
},
"HEADER_TEXT": {
"LABEL": "Header Text",
"PLACEHOLDER": "Portal header text",
"HELP_TEXT": "Portal header text",
"ERROR": "Portal header text is required"
},
"BUTTONS": {
"CREATE": "Create portal",
"CANCEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Portal created successfully.",
"ERROR_MESSAGE": "Couldn't create the portal. Try again."
}
}
},
"TABLE": {

View file

@ -0,0 +1,185 @@
<template>
<modal :show.sync="show" :on-close="onClose">
<woot-modal-header
:header-title="$t('HELP_CENTER.PORTAL.ADD.TITLE')"
:header-content="$t('HELP_CENTER.PORTAL.ADD.SUB_TITLE')"
/>
<form class="row" @submit.prevent="onCreate">
<div class="medium-12 columns">
<woot-input
v-model="name"
:class="{ error: $v.name.$error }"
class="medium-12 columns"
:error="$v.name.$error ? $t('HELP_CENTER.PORTAL.ADD.NAME.ERROR') : ''"
: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')"
@blur="$v.name.$touch"
@input="onNameChange"
/>
<woot-input
v-model="slug"
:class="{ error: $v.slug.$error }"
class="medium-12 columns"
:error="$v.slug.$error ? $t('HELP_CENTER.PORTAL.ADD.SLUG.ERROR') : ''"
: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')"
@blur="$v.slug.$touch"
/>
<woot-input
v-model="pageTitle"
class="medium-12 columns"
: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')"
/>
<woot-input
v-model="headerText"
class="medium-12 columns"
: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')"
/>
<woot-input
v-model="domain"
class="medium-12 columns"
: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')"
/>
<woot-input
v-model="homePageLink"
class="medium-12 columns"
: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 class="medium-12 columns">
<div class="modal-footer justify-content-end w-full">
<woot-button
class="button clear"
:is-loading="uiFlags.isCreating"
@click.prevent="onClose"
>
{{ $t('HELP_CENTER.PORTAL.ADD.BUTTONS.CANCEL') }}
</woot-button>
<woot-button>
{{ $t('HELP_CENTER.PORTAL.ADD.BUTTONS.CREATE') }}
</woot-button>
</div>
</div>
</div>
</form>
</modal>
</template>
<script>
import { mapGetters } from 'vuex';
import Modal from 'dashboard/components/Modal';
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import { convertToPortalSlug } from 'dashboard/helper/commons.js';
export default {
components: {
Modal,
},
mixins: [alertMixin],
props: {
show: {
type: Boolean,
default: true,
},
portalName: {
type: String,
default: '',
},
locale: {
type: String,
default: '',
},
},
data() {
return {
name: '',
slug: '',
domain: '',
homePageLink: '',
pageTitle: '',
headerText: '',
alertMessage: '',
};
},
computed: {
...mapGetters({
uiFlags: 'portals/uiFlagsIn',
}),
},
validations: {
name: {
required,
},
slug: {
required,
},
},
methods: {
onNameChange() {
this.slug = convertToPortalSlug(this.name);
},
async onCreate() {
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,
// TODO: add support for choosing color
color: '#1f93ff',
homepage_link: this.homePageLink,
page_title: this.pageTitle,
header_text: this.headerText,
config: {
// TODO: add support for choosing locale
allowed_locales: ['en'],
},
},
});
this.alertMessage = this.$t(
'HELP_CENTER.PORTAL.ADD.API.SUCCESS_MESSAGE'
);
this.$emit('cancel');
} catch (error) {
this.alertMessage =
error?.message || this.$t('HELP_CENTER.PORTAL.ADD.API.ERROR_MESSAGE');
} finally {
this.showAlert(this.alertMessage);
}
},
onClose() {
this.$emit('cancel');
},
},
};
</script>
<style scoped lang="scss">
.input-container::v-deep {
margin: 0 0 var(--space-normal);
input {
margin-bottom: 0;
}
.message {
margin-top: 0;
}
}
</style>

View file

@ -2,7 +2,7 @@
<div class="container">
<div class="header-wrap">
<h1 class="page-title">{{ $t('HELP_CENTER.PORTAL.HEADER') }}</h1>
<woot-button color-scheme="primary" size="small" @click="createPortal">
<woot-button color-scheme="primary" size="small" @click="addPortal">
{{ $t('HELP_CENTER.PORTAL.NEW_BUTTON') }}
</woot-button>
</div>
@ -22,6 +22,9 @@
:title="$t('HELP_CENTER.PORTAL.NO_PORTALS_MESSAGE')"
/>
</div>
<woot-modal :show.sync="isAddModalOpen" :on-close="closeModal">
<add-portal :show="isAddModalOpen" @cancel="closeModal" />
</woot-modal>
</div>
</template>
@ -30,11 +33,18 @@ import { mapGetters } from 'vuex';
import PortalListItem from '../../components/PortalListItem';
import Spinner from 'shared/components/Spinner.vue';
import EmptyState from 'dashboard/components/widgets/EmptyState';
import AddPortal from '../../components/AddPortal';
export default {
components: {
PortalListItem,
EmptyState,
Spinner,
AddPortal,
},
data() {
return {
isAddModalOpen: false,
};
},
computed: {
...mapGetters({
@ -50,8 +60,11 @@ export default {
},
},
methods: {
createPortal() {
this.$emit('create-portal');
addPortal() {
this.isAddModalOpen = !this.isAddModalOpen;
},
closeModal() {
this.isAddModalOpen = false;
},
},
};

View file

@ -26,13 +26,20 @@ export const actions = {
}
},
create: async ({ commit }, params) => {
create: async ({ commit, state, dispatch }, params) => {
commit(types.SET_UI_FLAG, { isCreating: true });
try {
const { data } = await portalAPIs.create(params);
const { id: portalId } = data;
commit(types.ADD_PORTAL_ENTRY, data);
commit(types.ADD_PORTAL_ID, portalId);
const {
portals: { selectedPortalId },
} = state;
// Check if there are any selected portal
if (!selectedPortalId) {
dispatch('setPortalId', portalId);
}
} catch (error) {
throwErrorMessage(error);
} finally {

View file

@ -43,7 +43,7 @@ describe('#actions', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({ data: apiResponse.payload[1] });
await actions.create(
{ commit },
{ commit, dispatch, state: { portals: { selectedPortalId: null } } },
{
color: 'red',
custom_domain: 'domain_for_help',
@ -59,7 +59,12 @@ describe('#actions', () => {
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
await expect(actions.create({ commit }, {})).rejects.toThrow(Error);
await expect(
actions.create(
{ commit, dispatch, state: { portals: { selectedPortalId: null } } },
{}
)
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.SET_UI_FLAG, { isCreating: true }],
[types.SET_UI_FLAG, { isCreating: false }],