feat: Add the ability to create dashboard apps from the UI (#4924)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Fayaz Ahmed
2022-07-08 14:25:32 +05:30
committed by GitHub
parent e4b159dd54
commit ef1d117717
14 changed files with 713 additions and 64 deletions

View File

@@ -0,0 +1,174 @@
<template>
<woot-modal :show="show" :on-close="closeModal">
<div class="column content-box">
<woot-modal-header :header-title="header" />
<form class="row" @submit.prevent="submit">
<woot-input
v-model.trim="app.title"
:class="{ error: $v.app.title.$error }"
class="medium-12 columns"
:label="$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.TITLE_LABEL')"
:placeholder="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.TITLE_PLACEHOLDER')
"
:error="
$v.app.title.$error
? $t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.TITLE_ERROR')
: null
"
data-testid="app-title"
@input="$v.app.title.$touch"
/>
<woot-input
v-model.trim="app.content.url"
:class="{ error: $v.app.content.url.$error }"
class="medium-12 columns app--url_input"
:label="$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.URL_LABEL')"
:placeholder="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.URL_PLACEHOLDER')
"
:error="
$v.app.content.url.$error
? $t('INTEGRATION_SETTINGS.DASHBOARD_APPS.FORM.URL_ERROR')
: null
"
data-testid="app-url"
@input="$v.app.content.url.$touch"
/>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-button
:is-loading="isLoading"
:is-disabled="$v.$invalid"
data-testid="label-submit"
>
{{ submitButtonLabel }}
</woot-button>
<woot-button class="button clear" @click.prevent="closeModal">
{{ $t('INTEGRATION_SETTINGS.DASHBOARD_APPS.CREATE.FORM_CANCEL') }}
</woot-button>
</div>
</div>
</form>
</div>
</woot-modal>
</template>
<script>
import { required, url } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
export default {
mixins: [alertMixin],
props: {
show: {
type: Boolean,
default: false,
},
mode: {
type: String,
default: 'create',
},
selectedAppData: {
type: Object,
default: () => ({}),
},
},
validations: {
app: {
title: { required },
content: {
type: { required },
url: { required, url },
},
},
},
data() {
return {
isLoading: false,
app: {
title: '',
content: {
type: 'frame',
url: '',
},
},
};
},
computed: {
header() {
return this.$t(`INTEGRATION_SETTINGS.DASHBOARD_APPS.${this.mode}.HEADER`);
},
submitButtonLabel() {
return this.$t(
`INTEGRATION_SETTINGS.DASHBOARD_APPS.${this.mode}.FORM_SUBMIT`
);
},
},
mounted() {
if (this.mode === 'UPDATE' && this.selectedAppData) {
this.app.title = this.selectedAppData.title;
this.app.content = this.selectedAppData.content[0];
}
},
methods: {
closeModal() {
// Reset the data once closed
this.app = {
title: '',
content: { type: 'frame', url: '' },
};
this.$emit('close');
},
async submit() {
try {
this.$v.$touch();
if (this.$v.$invalid) {
return;
}
const action = this.mode.toLowerCase();
const payload = {
title: this.app.title,
content: [this.app.content],
};
if (action === 'update') {
payload.id = this.selectedAppData.id;
}
this.isLoading = true;
await this.$store.dispatch(`dashboardApps/${action}`, payload);
this.showAlert(
this.$t(
`INTEGRATION_SETTINGS.DASHBOARD_APPS.${this.mode}.API_SUCCESS`
)
);
this.closeModal();
} catch (err) {
this.showAlert(
this.$t(`INTEGRATION_SETTINGS.DASHBOARD_APPS.${this.mode}.API_ERROR`)
);
} finally {
this.isLoading = false;
}
},
},
};
</script>
<style scoped lang="scss">
.content-row {
display: flex;
align-items: center;
width: 100%;
.app--url_input {
flex: 1;
}
.app--url_add_btn {
margin-left: var(--space-one);
margin-top: var(--space-one);
}
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<tr>
<td>{{ app.title }}</td>
<td>{{ app.content[0].url }}</td>
<td class="button-wrapper">
<woot-button
v-tooltip.top="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.LIST.EDIT_TOOLTIP')
"
variant="smooth"
size="tiny"
color-scheme="secondary"
class-names="grey-btn"
icon="edit"
@click="$emit('edit', app)"
/>
<woot-button
v-tooltip.top="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.LIST.DELETE_TOOLTIP')
"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="dismiss-circle"
class-names="grey-btn"
@click="$emit('delete', app)"
/>
</td>
</tr>
</template>
<script>
export default {
props: {
app: {
type: Object,
default: () => ({}),
},
},
loading: {
type: Boolean,
default: false,
},
};
</script>

View File

@@ -0,0 +1,158 @@
<template>
<div class="row content-box full-height">
<woot-button
color-scheme="success"
class-names="button--fixed-right-top"
icon="add-circle"
@click="openCreatePopup"
>
{{ $t('INTEGRATION_SETTINGS.DASHBOARD_APPS.HEADER_BTN_TXT') }}
</woot-button>
<div class="row">
<div class="small-8 columns with-right-space ">
<p
v-if="!uiFlags.isFetching && !records.length"
class="no-items-error-message"
>
{{ $t('INTEGRATION_SETTINGS.DASHBOARD_APPS.LIST.404') }}
</p>
<woot-loading-state
v-if="uiFlags.isFetching"
:message="$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.LIST.LOADING')"
/>
<table v-if="!uiFlags.isFetching && records.length" class="woot-table">
<thead>
<th
v-for="thHeader in $t(
'INTEGRATION_SETTINGS.DASHBOARD_APPS.LIST.TABLE_HEADER'
)"
:key="thHeader"
>
{{ thHeader }}
</th>
</thead>
<tbody>
<dashboard-apps-row
v-for="(dashboardAppItem, index) in records"
:key="dashboardAppItem.id"
:index="index"
:app="dashboardAppItem"
@edit="editApp"
@delete="openDeletePopup"
/>
</tbody>
</table>
</div>
<div class="small-4 columns">
<span
v-dompurify-html="
useInstallationName(
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.SIDEBAR_TXT'),
globalConfig.installationName
)
"
/>
</div>
</div>
<dashboard-app-modal
v-if="showDashboardAppPopup"
:show="showDashboardAppPopup"
:mode="mode"
:selected-app-data="selectedApp"
@close="toggleDashboardAppPopup"
/>
<woot-delete-modal
:show.sync="showDeleteConfirmationPopup"
:on-close="closeDeletePopup"
:on-confirm="confirmDeletion"
:title="$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.TITLE')"
:message="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.MESSAGE', {
appName: selectedApp.title,
})
"
:confirm-text="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.CONFIRM_YES')
"
:reject-text="$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.CONFIRM_NO')"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import DashboardAppModal from './DashboardAppModal.vue';
import DashboardAppsRow from './DashboardAppsRow.vue';
import alertMixin from 'shared/mixins/alertMixin';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
export default {
components: {
DashboardAppModal,
DashboardAppsRow,
},
mixins: [alertMixin, globalConfigMixin],
data() {
return {
loading: {},
showDashboardAppPopup: false,
showDeleteConfirmationPopup: false,
selectedApp: {},
mode: 'CREATE',
};
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
records: 'dashboardApps/getRecords',
uiFlags: 'dashboardApps/getUIFlags',
}),
},
mounted() {
this.$store.dispatch('dashboardApps/get');
},
methods: {
toggleDashboardAppPopup() {
this.showDashboardAppPopup = !this.showDashboardAppPopup;
this.selectedApp = {};
},
openDeletePopup(response) {
this.showDeleteConfirmationPopup = true;
this.selectedApp = response;
},
openCreatePopup() {
this.mode = 'CREATE';
this.selectedApp = {};
this.showDashboardAppPopup = true;
},
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
editApp(app) {
this.loading[app.id] = true;
this.mode = 'UPDATE';
this.selectedApp = app;
this.showDashboardAppPopup = true;
},
confirmDeletion() {
this.loading[this.selectedApp.id] = true;
this.closeDeletePopup();
this.deleteApp(this.selectedApp.id);
},
async deleteApp(id) {
try {
await this.$store.dispatch('dashboardApps/delete', id);
this.showAlert(
this.$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.API_SUCCESS')
);
} catch (error) {
this.showAlert(
this.$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DELETE.API_ERROR')
);
}
},
},
};
</script>

View File

@@ -17,6 +17,20 @@
:integration-action="item.action"
/>
</div>
<div class="small-12 columns integration">
<integration
integration-id="dashboard-apps"
integration-logo="dashboard-apps.svg"
:integration-name="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.TITLE')
"
:integration-description="
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DESCRIPTION')
"
integration-enabled
integration-action="/dashboard-apps"
/>
</div>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import Index from './Index';
import SettingsContent from '../Wrapper';
import Webhook from './Webhooks/Index';
import DashboardApps from './DashboardApps/Index';
import ShowIntegration from './ShowIntegration';
import { frontendURL } from '../../../../helper/URLHelper';
@@ -35,6 +36,12 @@ export default {
name: 'settings_integrations_webhook',
roles: ['administrator'],
},
{
path: 'dashboard-apps',
component: DashboardApps,
name: 'settings_integrations_dashboard_apps',
roles: ['administrator'],
},
{
path: ':integration_id',
name: 'settings_integrations_integration',