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:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user