Fix url in emails, add frontendURL helper (#19)

Fixes #16
This commit is contained in:
Pranav Raj S
2019-08-25 19:59:28 +05:30
committed by GitHub
parent 28fdc062de
commit bd7bd63aae
89 changed files with 550 additions and 398 deletions

View File

@@ -7,6 +7,7 @@
import moment from 'moment';
import Cookies from 'js-cookie';
import endPoints from './endPoints';
import { frontendURL } from '../helper/URLHelper';
export default {
login(creds) {
@@ -65,7 +66,7 @@ export default {
if (error.response.status === 401) {
Cookies.remove('auth_data');
Cookies.remove('user');
window.location = '/login';
window.location = frontendURL('login');
}
reject(error);
});
@@ -80,7 +81,7 @@ export default {
.then(response => {
Cookies.remove('auth_data');
Cookies.remove('user');
window.location = '/u/login';
window.location = frontendURL('login');
resolve(response);
})
.catch(error => {

View File

@@ -1,8 +1,8 @@
<template>
<button type="submit" :disabled="disabled" :class="computedClass">
<i :class="iconClass" class="icon" v-if="!!iconClass"></i>
<span>{{buttonText}}</span>
<spinner v-if="loading"/>
<i v-if="!!iconClass" :class="iconClass" class="icon"></i>
<span>{{ buttonText }}</span>
<spinner v-if="loading" />
</button>
</template>
@@ -10,19 +10,34 @@
import Spinner from '../Spinner';
export default {
props: {
disabled: Boolean,
loading: Boolean,
buttonText: String,
buttonClass: String,
iconClass: String,
},
components: {
Spinner,
},
props: {
disabled: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
buttonText: {
type: String,
default: '',
},
buttonClass: {
type: String,
default: '',
},
iconClass: {
type: String,
default: '',
},
},
computed: {
computedClass() {
return `button ${this.buttonClass || ' '}`;
return `button nice ${this.buttonClass || ' '}`;
},
},
};

View File

@@ -1,8 +1,13 @@
<template>
<button type="button" v-on:click="toggleStatus" class="button round resolve--button" :class="buttonClass">
<i class="icon" :class="buttonIconClass" v-if="!isLoading"></i>
<spinner v-if="isLoading"/>
{{ currentStatus }}
<button
type="button"
class="button nice resolve--button"
:class="buttonClass"
@click="toggleStatus"
>
<i v-if="!isLoading" class="icon" :class="buttonIconClass"></i>
<spinner v-if="isLoading" />
{{ currentStatus }}
</button>
</template>
@@ -13,9 +18,7 @@ import { mapGetters } from 'vuex';
import Spinner from '../Spinner';
export default {
props: [
'conversationId',
],
props: ['conversationId'],
data() {
return {
isLoading: false,
@@ -50,5 +53,3 @@ export default {
},
};
</script>

View File

@@ -1,47 +1,56 @@
<template>
<aside class="sidebar animated shrink columns">
<div class="logo">
<router-link to="{ path: '/u/dashboard' }" replace>
<img src="~assets/images/woot-logo.svg" alt="Woot-logo"/>
<router-link :to="dashboardPath" replace>
<img src="~assets/images/woot-logo.svg" alt="Woot-logo" />
</router-link>
</div>
<div class="main-nav">
<transition-group name="menu-list" tag="ul" class="menu vertical">
<sidebar-item
v-for="item in sidebarItems"
v-for="item in accessibleMenuItems"
:key="item.toState"
:menu-item="item"
:key="item"
v-if="showItem(item)"
/>
</transition-group>
</div>
<transition name="fade" mode="out-in">
<woot-status-bar
v-if="shouldShowStatusBox"
:message="trialMessage"
:buttonText="$t('APP_GLOBAL.TRAIL_BUTTON')"
:buttonRoute="{ name: 'billing' }"
:button-text="$t('APP_GLOBAL.TRAIL_BUTTON')"
:button-route="{ name: 'billing' }"
:type="statusBarClass"
:show-button="isAdmin()"
v-if="shouldShowStatusBox"
/>
</transition>
<div class="bottom-nav">
<transition name="menu-slide">
<div class="dropdown-pane top" v-if="showOptionsMenu" v-on-clickaway="showOptions">
<div
v-if="showOptionsMenu"
v-on-clickaway="showOptions"
class="dropdown-pane top"
>
<ul class="vertical dropdown menu">
<li><a href="#">Help & Support</a></li>
<!-- <li><a href="#">Help & Support</a></li> -->
<li><a href="#" @click.prevent="logout()">Logout</a></li>
</ul>
</div>
</transition>
<div class="current-user" @click.prevent="showOptions()">
<img class="current-user--thumbnail" :src="gravatarUrl()"/>
<img class="current-user--thumbnail" :src="gravatarUrl()" />
<div class="current-user--data">
<h3 class="current-user--name">{{ currentUser.name }}</h3>
<h5 class="current-user--role">{{ currentUser.role }}</h5>
<h3 class="current-user--name">
{{ currentUser.name }}
</h3>
<h5 class="current-user--role">
{{ currentUser.role }}
</h5>
</div>
<span class="current-user--options icon ion-android-more-vertical"></span>
<span
class="current-user--options icon ion-android-more-vertical"
></span>
</div>
<!-- <router-link class="icon ion-arrow-graph-up-right" tag="span" to="/settings/reports" active-class="active"></router-link> -->
</div>
@@ -57,17 +66,20 @@ import adminMixin from '../../mixins/isAdmin';
import Auth from '../../api/auth';
import SidebarItem from './SidebarItem';
import WootStatusBar from '../widgets/StatusBar';
/* eslint-disable no-console */
import { frontendURL } from '../../helper/URLHelper';
export default {
mixins: [clickaway, adminMixin],
props: {
route: String,
route: {
type: String,
},
},
data() {
return {
showOptionsMenu: false,
};
},
mixins: [clickaway, adminMixin],
mounted() {
// this.$store.dispatch('fetchLabels');
this.$store.dispatch('fetchInboxes');
@@ -78,22 +90,30 @@ export default {
daysLeft: 'getTrialLeft',
subscriptionData: 'getSubscription',
}),
sidebarItems() {
// Get Current Route
accessibleMenuItems() {
const currentRoute = this.$store.state.route.name;
// get all keys in menuGroup
const groupKey = Object.keys(this.sidebarList);
let menuItems = [];
// Iterate over menuGroup to find the correct group
for (let i = 0; i < groupKey.length; i += 1) {
const groupItem = this.sidebarList[groupKey[i]];
// Check if current route is included
const isRouteIncluded = groupItem.routes.indexOf(currentRoute) > -1;
if (isRouteIncluded) {
return groupItem.menuItems;
menuItems = Object.values(groupItem.menuItems);
}
}
// If not found return empty array
return [];
const { role } = this.currentUser;
return menuItems.filter(
menuItem =>
window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
);
},
dashboardPath() {
return frontendURL('dashboard');
},
currentUser() {
return Auth.getCurrentUser();
@@ -102,12 +122,16 @@ export default {
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
},
shouldShowStatusBox() {
return this.subscriptionData.state === 'trial' || this.subscriptionData.state === 'cancelled';
return (
this.subscriptionData.state === 'trial' ||
this.subscriptionData.state === 'cancelled'
);
},
statusBarClass() {
if (this.subscriptionData.state === 'trial') {
return 'warning';
} else if (this.subscriptionData.state === 'cancelled') {
}
if (this.subscriptionData.state === 'cancelled') {
return 'danger';
}
return '';
@@ -122,11 +146,6 @@ export default {
const hash = md5(this.currentUser.email);
return `${window.WootConstants.GRAVATAR_URL}${hash}?d=monsterid`;
},
// Show if user has access to the route
showItem(item) {
const { role } = this.currentUser;
return window.roleWiseRoutes[role].indexOf(item.toStateName) > -1;
},
showOptions() {
this.showOptionsMenu = !this.showOptionsMenu;
},

View File

@@ -46,7 +46,7 @@
<span v-if="isAdmin()">
{{ $t('CONVERSATION.NO_INBOX_1') }}
<br />
<router-link to="/u/settings/inboxes/new">
<router-link :to="newInboxURL">
{{ $t('CONVERSATION.CLICK_HERE') }}
</router-link>
{{ $t('CONVERSATION.NO_INBOX_2') }}
@@ -88,6 +88,7 @@ import ReplyBox from './ReplyBox';
import Conversation from './Conversation';
import conversationMixin from '../../../mixins/conversations';
import adminMixin from '../../../mixins/isAdmin';
import { frontendURL } from '../../../helper/URLHelper';
export default {
components: {
@@ -169,6 +170,10 @@ export default {
);
},
newInboxURL() {
return frontendURL('settings/inboxes/new');
},
shouldLoadMoreChats() {
return !this.listLoadingStatus && !this.isLoadingPrevious;
},

View File

@@ -1,11 +1,34 @@
<template>
<div class="conversation" :class="{ active: isActiveChat, 'unread-chat': hasUnread }" @click="cardClick(chat)" >
<Thumbnail :src="chat.meta.sender.thumbnail" :badge="chat.meta.sender.channel" class="columns" />
<div
class="conversation"
:class="{ active: isActiveChat, 'unread-chat': hasUnread }"
@click="cardClick(chat)"
>
<Thumbnail
:src="chat.meta.sender.thumbnail"
:badge="chat.meta.sender.channel"
class="columns"
/>
<div class="conversation--details columns">
<h4 class="conversation--user">{{ chat.meta.sender.name }} <span class="label" v-tooltip.bottom="inboxName(chat.inbox_id)" v-if="isInboxNameVisible">{{inboxName(chat.inbox_id)}}</span> </h4>
<p class="conversation--message" v-html="extractMessageText(lastMessage(chat))"></p>
<h4 class="conversation--user">
{{ chat.meta.sender.name }}
<span
v-if="isInboxNameVisible"
v-tooltip.bottom="inboxName(chat.inbox_id)"
class="label"
>
{{ inboxName(chat.inbox_id) }}
</span>
</h4>
<p
class="conversation--message"
v-html="extractMessageText(lastMessage(chat))"
></p>
<div class="conversation--meta">
<span class="timestamp">{{ dynamicTime(lastMessage(chat).created_at) }}</span>
<span class="timestamp">
{{ dynamicTime(lastMessage(chat).created_at) }}
</span>
<span class="unread">{{ getUnreadCount }}</span>
</div>
</div>
@@ -14,27 +37,25 @@
<script>
/* eslint no-console: 0 */
/* eslint no-extra-boolean-cast: 0 */
/* global bus */
import { mapGetters } from 'vuex';
import Thumbnail from '../Thumbnail';
import getEmojiSVG from '../emoji/utils';
import conversationMixin from '../../../mixins/conversations';
import timeMixin from '../../../mixins/time';
import router from '../../../routes';
import { frontendURL } from '../../../helper/URLHelper';
export default {
props: [
'chat',
],
mixins: [timeMixin, conversationMixin],
components: {
Thumbnail,
},
created() {
// console.log(this.chat.inbox_id);
mixins: [timeMixin, conversationMixin],
props: {
chat: {
type: Object,
default: () => {},
},
},
computed: {
@@ -64,7 +85,7 @@ export default {
methods: {
cardClick(chat) {
router.push({
path: `/u/conversations/${chat.id}`,
path: frontendURL(`conversations/${chat.id}`),
});
},
extractMessageText(chatItem) {
@@ -85,7 +106,9 @@ export default {
},
getEmojiSVG,
inboxName(inboxId) {
const [stateInbox] = this.inboxesList.filter(inbox => inbox.channel_id === inboxId);
const [stateInbox] = this.inboxesList.filter(
inbox => inbox.channel_id === inboxId
);
return !stateInbox ? '' : stateInbox.label;
},
},

View File

@@ -0,0 +1,6 @@
import queryString from 'query-string';
export const frontendURL = (path, params) => {
const stringifiedParams = params ? `?${queryString.stringify(params)}` : '';
return `/app/${path}${stringifiedParams}`;
};

View File

@@ -1,3 +1,5 @@
import { frontendURL } from '../helper/URLHelper';
export default {
menuGroup: {
common: {
@@ -14,7 +16,7 @@ export default {
label: 'Conversations',
hasSubMenu: false,
key: '',
toState: '/u/dashboard',
toState: frontendURL('dashboard'),
toolTip: 'Conversation from all subscribed inboxes',
toStateName: 'home',
},
@@ -22,14 +24,14 @@ export default {
icon: 'ion-arrow-graph-up-right',
label: 'Reports',
hasSubMenu: false,
toState: '/u/reports',
toState: frontendURL('reports'),
toStateName: 'settings_account_reports',
},
settings: {
icon: 'ion-settings',
label: 'Settings',
hasSubMenu: false,
toState: '/u/settings/',
toState: frontendURL('settings'),
toStateName: 'settings_home',
},
inbox: {
@@ -39,7 +41,7 @@ export default {
newLink: true,
key: 'inbox',
cssClass: 'menu-title align-justify',
toState: '/u/settings/inboxes',
toState: frontendURL('settings/inboxes'),
toStateName: 'settings_inbox_list',
children: [],
},
@@ -65,41 +67,41 @@ export default {
label: 'Home',
hasSubMenu: false,
toStateName: 'home',
toState: '/u/dashboard',
toState: frontendURL('dashboard'),
},
agents: {
icon: 'ion-person-stalker',
label: 'Agents',
hasSubMenu: false,
toState: '/u/settings/agents/list',
toState: frontendURL('settings/agents/list'),
toStateName: 'agent_list',
},
inboxes: {
icon: 'ion-archive',
label: 'Inboxes',
hasSubMenu: false,
toState: '/u/settings/inboxes/list',
toState: frontendURL('settings/inboxes/list'),
toStateName: 'settings_inbox_list',
},
cannedResponses: {
icon: 'ion-chatbox-working',
label: 'Canned Responses',
hasSubMenu: false,
toState: '/u/settings/canned-response/list',
toState: frontendURL('settings/canned-response/list'),
toStateName: 'canned_list',
},
billing: {
icon: 'ion-card',
label: 'Billing',
hasSubMenu: false,
toState: '/u/settings/billing',
toState: frontendURL('settings/billing'),
toStateName: 'billing',
},
account: {
icon: 'ion-beer',
label: 'Account Settings',
hasSubMenu: false,
toState: '/u/settings/account',
toState: frontendURL('settings/account'),
toStateName: 'account',
},
},

View File

@@ -1,7 +1,7 @@
{
"INBOX_MGMT": {
"HEADER": "Inboxes",
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a Facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a new Facebook Page. </p><p> In the <a href=\"/u/dashboard\">Dashboard</a>, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a Facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a new Facebook Page. </p><p> In the <a href=\"/app/dashboard\">Dashboard</a>, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
"LIST": {
"404": "There are no inboxes attached to this account."
},

View File

@@ -1,13 +1,20 @@
<template>
<form class="login-box medium-4 column align-self-middle" v-on:submit.prevent="submit()">
<h4>{{$t('RESET_PASSWORD.TITLE')}}</h4>
<form
class="login-box medium-4 column align-self-middle"
@submit.prevent="submit()"
>
<h4>{{ $t('RESET_PASSWORD.TITLE') }}</h4>
<div class="column log-in-form">
<label :class="{ 'error': $v.credentials.email.$error }">
{{$t('RESET_PASSWORD.EMAIL.LABEL')}}
<input type="text" v-bind:placeholder="$t('RESET_PASSWORD.EMAIL.PLACEHOLDER')" v-model.trim="credentials.email" @input="$v.credentials.email.$touch">
<span class="message" v-if="$v.credentials.email.$error">
{{$t('RESET_PASSWORD.EMAIL.ERROR')}}
<label :class="{ error: $v.credentials.email.$error }">
{{ $t('RESET_PASSWORD.EMAIL.LABEL') }}
<input
v-model.trim="credentials.email"
type="text"
:placeholder="$t('RESET_PASSWORD.EMAIL.PLACEHOLDER')"
@input="$v.credentials.email.$touch"
/>
<span v-if="$v.credentials.email.$error" class="message">
{{ $t('RESET_PASSWORD.EMAIL.ERROR') }}
</span>
</label>
<woot-submit-button
@@ -24,6 +31,7 @@
/* global bus */
import { required, minLength, email } from 'vuelidate/lib/validators';
import Auth from '../../api/auth';
import { frontendURL } from '../../helper/URLHelper';
export default {
data() {
@@ -58,13 +66,13 @@ export default {
submit() {
this.resetPassword.showLoading = true;
Auth.resetPassword(this.credentials)
.then((res) => {
let message = this.$t('RESET_PASSWORD.API.SUCCESS_MESSAGE');
.then(res => {
let successMessage = this.$t('RESET_PASSWORD.API.SUCCESS_MESSAGE');
if (res.data && res.data.message) {
message = res.data.message;
successMessage = res.data.message;
}
this.showAlert(message);
window.location = '/login';
this.showAlert(successMessage);
window.location = frontendURL('login');
})
.catch(() => {
this.showAlert(this.$t('RESET_PASSWORD.API.ERROR_MESSAGE'));

View File

@@ -1,9 +1,17 @@
<template>
<div class="medium-10 column signup">
<div class="text-center medium-12 signup__hero">
<img src="~assets/images/woot-logo.svg" alt="Woot-logo" class="hero__logo" />
<h2 class="hero__title">{{$t('REGISTER.TRY_WOOT')}}</h2>
<p class="hero__sub">{{$t('REGISTER.TRY_WOOT_SUB')}}</p>
<img
src="~assets/images/woot-logo.svg"
alt="Woot-logo"
class="hero__logo"
/>
<h2 class="hero__title">
{{ $t('REGISTER.TRY_WOOT') }}
</h2>
<p class="hero__sub">
{{ $t('REGISTER.TRY_WOOT_SUB') }}
</p>
</div>
<div class="row align-center">
<div class="medium-5 column">
@@ -16,24 +24,38 @@
</ul>
</div>
<div class="medium-5 column">
<form class="signup-box login-box " v-on:submit.prevent="submit()">
<form class="signup-box login-box " @submit.prevent="submit()">
<div class="column log-in-form">
<label :class="{ 'error': $v.credentials.name.$error }">
{{$t('REGISTER.ACCOUNT_NAME.LABEL')}}
<input type="text" v-bind:placeholder="$t('REGISTER.ACCOUNT_NAME.PLACEHOLDER')" v-model.trim="credentials.name" @input="$v.credentials.name.$touch">
<span class="message" v-if="$v.credentials.name.$error">
{{$t('REGISTER.ACCOUNT_NAME.ERROR')}}
<label :class="{ error: $v.credentials.name.$error }">
{{ $t('REGISTER.ACCOUNT_NAME.LABEL') }}
<input
v-model.trim="credentials.name"
type="text"
:placeholder="$t('REGISTER.ACCOUNT_NAME.PLACEHOLDER')"
@input="$v.credentials.name.$touch"
/>
<span v-if="$v.credentials.name.$error" class="message">
{{ $t('REGISTER.ACCOUNT_NAME.ERROR') }}
</span>
</label>
<label :class="{ 'error': $v.credentials.email.$error }">
{{$t('REGISTER.EMAIL.LABEL')}}
<input type="email" v-bind:placeholder="$t('REGISTER.EMAIL.PLACEHOLDER')" v-model.trim="credentials.email" @input="$v.credentials.email.$touch">
<span class="message" v-if="$v.credentials.email.$error">
{{$t('REGISTER.EMAIL.ERROR')}}
<label :class="{ error: $v.credentials.email.$error }">
{{ $t('REGISTER.EMAIL.LABEL') }}
<input
v-model.trim="credentials.email"
type="email"
:placeholder="$t('REGISTER.EMAIL.PLACEHOLDER')"
@input="$v.credentials.email.$touch"
/>
<span v-if="$v.credentials.email.$error" class="message">
{{ $t('REGISTER.EMAIL.ERROR') }}
</span>
</label>
<woot-submit-button
:disabled="$v.credentials.name.$invalid || $v.credentials.email.$invalid || register.showLoading"
:disabled="
$v.credentials.name.$invalid ||
$v.credentials.email.$invalid ||
register.showLoading
"
:button-text="$t('REGISTER.SUBMIT')"
:loading="register.showLoading"
button-class="large expanded"
@@ -58,6 +80,7 @@
import { required, minLength, email } from 'vuelidate/lib/validators';
import Auth from '../../api/auth';
import { frontendURL } from '../../helper/URLHelper';
export default {
data() {
@@ -96,12 +119,12 @@ export default {
submit() {
this.register.showLoading = true;
Auth.register(this.credentials)
.then((res) => {
.then(res => {
if (res.status === 200) {
window.location = '/u/dashboard';
window.location = frontendURL('dashboard');
}
})
.catch((error) => {
.catch(error => {
let errorMessage = this.$t('REGISTER.API.ERROR_MESSAGE');
if (error.response && error.response.data.message) {
errorMessage = error.response.data.message;

View File

@@ -3,11 +3,12 @@ import Confirmation from './Confirmation';
import Signup from './Signup';
import PasswordEdit from './PasswordEdit';
import ResetPassword from './ResetPassword';
import { frontendURL } from '../../helper/URLHelper';
export default {
routes: [
{
path: '/u/auth',
path: frontendURL('auth'),
name: 'auth',
component: Auth,
children: [

View File

@@ -1,10 +1,11 @@
/* eslint arrow-body-style: 0 */
import ConversationView from './ConversationView';
import { frontendURL } from '../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/dashboard',
path: frontendURL('dashboard'),
name: 'home',
roles: ['administrator', 'agent'],
component: ConversationView,
@@ -13,7 +14,7 @@ export default {
},
},
{
path: '/u/inbox/:inbox_id',
path: frontendURL('inbox/:inbox_id'),
name: 'inbox_dashboard',
roles: ['administrator', 'agent'],
component: ConversationView,
@@ -22,7 +23,7 @@ export default {
},
},
{
path: '/u/conversations/:conversation_id',
path: frontendURL('conversations/:conversation_id'),
name: 'inbox_conversation',
roles: ['administrator', 'agent'],
component: ConversationView,

View File

@@ -1,11 +1,12 @@
import AppContainer from './Dashboard';
import settings from './settings/settings.routes';
import conversation from './conversation/conversation.routes';
import { frontendURL } from '../../helper/URLHelper';
export default {
routes: [
{
path: '/u/',
path: frontendURL(''),
component: AppContainer,
children: [...conversation.routes, ...settings.routes],
},

View File

@@ -1,7 +1,7 @@
<template>
<div class="column content-box">
<button
class="button icon success btn-fixed-right-top"
class="button nice icon success btn-fixed-right-top"
@click="openAddPopup()"
>
<i class="icon ion-android-add-circle"></i>
@@ -12,13 +12,20 @@
<!-- List Agents -->
<div class="row">
<div class="small-8 columns">
<woot-loading-state v-if="fetchStatus" :message="$t('AGENT_MGMT.LOADING')" />
<p v-if="!fetchStatus && !agentList.length">{{ $t('AGENT_MGMT.LIST.404') }}</p>
<table class="woot-table" v-if="!fetchStatus && agentList.length">
<woot-loading-state
v-if="fetchStatus"
:message="$t('AGENT_MGMT.LOADING')"
/>
<p v-if="!fetchStatus && !agentList.length">
{{ $t('AGENT_MGMT.LIST.404') }}
</p>
<table v-if="!fetchStatus && agentList.length" class="woot-table">
<tbody>
<tr v-for="(agent, index) in agentList">
<tr v-for="(agent, index) in agentList" :key="agent.email">
<!-- Gravtar Image -->
<td><img class="woot-thumbnail" :src="gravatarUrl(agent.email)"/></td>
<td>
<img class="woot-thumbnail" :src="gravatarUrl(agent.email)" />
</td>
<!-- Agent Name + Email -->
<td>
<span class="agent-name">{{ agent.name }}</span>
@@ -27,17 +34,21 @@
<!-- Agent Role + Verification Status -->
<td>
<span class="agent-name">{{ agent.role }}</span>
<span v-if="agent.confirmed">{{$t('AGENT_MGMT.LIST.VERIFIED')}}</span>
<span v-if="!agent.confirmed">{{$t('AGENT_MGMT.LIST.VERIFICATION_PENDING')}}</span>
<span v-if="agent.confirmed">
{{ $t('AGENT_MGMT.LIST.VERIFIED') }}
</span>
<span v-if="!agent.confirmed">
{{ $t('AGENT_MGMT.LIST.VERIFICATION_PENDING') }}
</span>
</td>
<!-- Actions -->
<td>
<div v-if="showActions(agent)" class="button-wrapper">
<div @click="openEditPopup(agent)">
<woot-submit-button
:button-text="$t('AGENT_MGMT.EDIT.BUTTON_TEXT')"
icon-class="ion-edit"
button-class="link hollow grey-btn"
:button-text="$t('AGENT_MGMT.EDIT.BUTTON_TEXT')"
icon-class="ion-edit"
button-class="link hollow grey-btn"
/>
</div>
<div @click="openDeletePopup(agent, index)">
@@ -60,18 +71,18 @@
</div>
<!-- Add Agent -->
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
<add-agent :on-close="hideAddPopup"/>
<add-agent :on-close="hideAddPopup" />
</woot-modal>
<!-- Edit Agent -->
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<edit-agent
v-if="showEditPopup"
:id="currentAgent.id"
:name="currentAgent.name"
:type="currentAgent.role"
:email="currentAgent.email"
:id="currentAgent.id"
:on-close="hideEditPopup"
v-if="showEditPopup"
/>
</woot-modal>
@@ -87,9 +98,7 @@
/>
<!-- Loader Status -->
</div>
</template>
<script>
/* global bus */
@@ -97,14 +106,12 @@
import { mapGetters } from 'vuex';
import md5 from 'md5';
import PageHeader from '../SettingsSubPageHeader';
import AddAgent from './AddAgent';
import EditAgent from './EditAgent';
import DeleteAgent from './DeleteAgent';
export default {
components: {
PageHeader,
AddAgent,
EditAgent,
DeleteAgent,
@@ -127,13 +134,19 @@ export default {
fetchStatus: 'getAgentFetchStatus',
}),
deleteConfirmText() {
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.YES')} ${this.currentAgent.name}`;
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.YES')} ${
this.currentAgent.name
}`;
},
deleteRejectText() {
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.NO')} ${this.currentAgent.name}`;
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.NO')} ${
this.currentAgent.name
}`;
},
deleteMessage() {
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.MESSAGE')} ${this.currentAgent.name} ?`;
return `${this.$t('AGENT_MGMT.DELETE.CONFIRM.MESSAGE')} ${
this.currentAgent.name
} ?`;
},
},
mounted() {
@@ -142,7 +155,9 @@ export default {
methods: {
showActions(agent) {
if (agent.role === 'administrator') {
const adminList = this.agentList.filter(item => item.role === 'administrator');
const adminList = this.agentList.filter(
item => item.role === 'administrator'
);
return adminList.length !== 1;
}
return true;
@@ -184,19 +199,22 @@ export default {
this.deleteAgent(this.currentAgent.id);
},
deleteAgent(id) {
this.$store.dispatch('deleteAgent', {
id,
}).then(() => {
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.SUCCESS_MESSAGE'));
}).catch(() => {
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.ERROR_MESSAGE'));
});
this.$store
.dispatch('deleteAgent', {
id,
})
.then(() => {
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.SUCCESS_MESSAGE'));
})
.catch(() => {
this.showAlert(this.$t('AGENT_MGMT.DELETE.API.ERROR_MESSAGE'));
});
},
// Show SnackBar
showAlert(message) {
// Reset loading, current selected agent
this.loading[this.currentAgent.id] = false;
this.currentAgent = { };
this.currentAgent = {};
// Show message
this.agentAPI.message = message;
bus.$emit('newToastMessage', message);

View File

@@ -1,10 +1,11 @@
import SettingsContent from '../Wrapper';
import AgentHome from './Index';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/settings/agents',
path: frontendURL('settings/agents'),
component: SettingsContent,
props: {
headerTitle: 'AGENT_MGMT.HEADER',

View File

@@ -1,11 +1,12 @@
import Index from './Index';
import SettingsContent from '../Wrapper';
import AccountLocked from './AccountLocked';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/settings/billing',
path: frontendURL('settings/billing'),
component: SettingsContent,
props: {
headerTitle: 'BILLING.HEADER',

View File

@@ -1,7 +1,7 @@
<template>
<div class="column content-box">
<button
class="button icon success btn-fixed-right-top"
class="button nice icon success btn-fixed-right-top"
@click="openAddPopup()"
>
<i class="icon ion-android-add-circle"></i>
@@ -10,31 +10,46 @@
<!-- List Canned Response -->
<div class="row">
<div class="small-8 columns">
<p v-if="!fetchStatus && !cannedResponseList.length" class="no-items-error-message">
<p
v-if="!fetchStatus && !cannedResponseList.length"
class="no-items-error-message"
>
{{ $t('CANNED_MGMT.LIST.404') }}
</p>
<woot-loading-state v-if="fetchStatus" :message="$t('CANNED_MGMT.LOADING')" />
<woot-loading-state
v-if="fetchStatus"
:message="$t('CANNED_MGMT.LOADING')"
/>
<table class="woot-table" v-if="!fetchStatus && cannedResponseList.length">
<table
v-if="!fetchStatus && cannedResponseList.length"
class="woot-table"
>
<thead>
<!-- Header -->
<th v-for="thHeader in $t('CANNED_MGMT.LIST.TABLE_HEADER')">
<th
v-for="thHeader in $t('CANNED_MGMT.LIST.TABLE_HEADER')"
:key="thHeader"
>
{{ thHeader }}
</th>
</thead>
<tbody>
<tr v-for="(cannedItem, index) in cannedResponseList">
<tr
v-for="(cannedItem, index) in cannedResponseList"
:key="cannedItem.short_code"
>
<!-- Short Code -->
<td>{{ cannedItem.short_code }}</td>
<!-- Content -->
<td>{{ cannedItem.content }}</td>
<!-- Action Buttons -->
<td class="button-wrapper">
<div @click="openEditPopup(cannedItem)" >
<div @click="openEditPopup(cannedItem)">
<woot-submit-button
:button-text="$t('CANNED_MGMT.EDIT.BUTTON_TEXT')"
icon-class="ion-edit"
button-class="link hollow grey-btn"
:button-text="$t('CANNED_MGMT.EDIT.BUTTON_TEXT')"
icon-class="ion-edit"
button-class="link hollow grey-btn"
/>
</div>
<div @click="openDeletePopup(cannedItem, index)">
@@ -57,17 +72,17 @@
</div>
<!-- Add Agent -->
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
<add-canned :on-close="hideAddPopup"/>
<add-canned :on-close="hideAddPopup" />
</woot-modal>
<!-- Edit Canned Response -->
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<edit-canned
v-if="showEditPopup"
:id="selectedResponse.id"
:edshort-code="selectedResponse.short_code"
:edcontent="selectedResponse.content"
:id="selectedResponse.id"
:on-close="hideEditPopup"
v-if="showEditPopup"
/>
</woot-modal>
@@ -82,13 +97,11 @@
:reject-text="deleteRejectText"
/>
</div>
</template>
<script>
/* global bus */
import { mapGetters } from 'vuex';
import PageHeader from '../SettingsSubPageHeader';
import AddCanned from './AddCanned';
import EditCanned from './EditCanned';
import DeleteCanned from './DeleteCanned';
@@ -96,7 +109,6 @@ import DeleteCanned from './DeleteCanned';
export default {
components: {
AddCanned,
PageHeader,
EditCanned,
DeleteCanned,
},
@@ -119,13 +131,19 @@ export default {
}),
// Delete Modal
deleteConfirmText() {
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.YES')} ${this.selectedResponse.short_code}`;
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.YES')} ${
this.selectedResponse.short_code
}`;
},
deleteRejectText() {
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.NO')} ${this.selectedResponse.short_code}`;
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.NO')} ${
this.selectedResponse.short_code
}`;
},
deleteMessage() {
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.MESSAGE')} ${this.selectedResponse.short_code} ?`;
return `${this.$t('CANNED_MGMT.DELETE.CONFIRM.MESSAGE')} ${
this.selectedResponse.short_code
} ?`;
},
},
mounted() {
@@ -136,7 +154,7 @@ export default {
showAlert(message) {
// Reset loading, current selected agent
this.loading[this.selectedResponse.id] = false;
this.selectedResponse = { };
this.selectedResponse = {};
// Show message
this.cannedResponseAPI.message = message;
bus.$emit('newToastMessage', message);
@@ -173,13 +191,16 @@ export default {
this.deleteCannedResponse(this.selectedResponse.id);
},
deleteCannedResponse(id) {
this.$store.dispatch('deleteCannedResponse', {
id,
}).then(() => {
this.showAlert(this.$t('CANNED_MGMT.DELETE.API.SUCCESS_MESSAGE'));
}).catch(() => {
this.showAlert(this.$t('CANNED_MGMT.DELETE.API.ERROR_MESSAGE'));
});
this.$store
.dispatch('deleteCannedResponse', {
id,
})
.then(() => {
this.showAlert(this.$t('CANNED_MGMT.DELETE.API.SUCCESS_MESSAGE'));
})
.catch(() => {
this.showAlert(this.$t('CANNED_MGMT.DELETE.API.ERROR_MESSAGE'));
});
},
},
};

View File

@@ -1,10 +1,11 @@
import SettingsContent from '../Wrapper';
import CannedHome from './Index';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/settings/canned-response',
path: frontendURL('settings/canned-response'),
component: SettingsContent,
props: {
headerTitle: 'CANNED_MGMT.HEADER',

View File

@@ -5,15 +5,24 @@
<div class="small-8 columns">
<p v-if="!inboxesList.length" class="no-items-error-message">
{{ $t('INBOX_MGMT.LIST.404') }}
<router-link to="/u/settings/inboxes/new" v-if="isAdmin()">
<router-link
v-if="isAdmin()"
:to="frontendURL('settings/inboxes/new')"
>
{{ $t('SETTINGS.INBOXES.NEW_INBOX') }}
</router-link>
</p>
<table class="woot-table" v-if="inboxesList.length">
<table v-if="inboxesList.length" class="woot-table">
<tbody>
<tr v-for="item in inboxesList">
<td><img class="woot-thumbnail" :src="item.avatarUrl" alt="No Page Image"/></td>
<tr v-for="item in inboxesList" :key="item.label">
<td>
<img
class="woot-thumbnail"
:src="item.avatarUrl"
alt="No Page Image"
/>
</td>
<!-- Short Code -->
<td>
<span class="agent-name">{{ item.label }}</span>
@@ -23,11 +32,11 @@
<!-- Action Buttons -->
<td>
<div class="button-wrapper">
<div @click="openSettings(item)" v-if="isAdmin()">
<div v-if="isAdmin()" @click="openSettings(item)">
<woot-submit-button
:button-text="$t('INBOX_MGMT.SETTINGS')"
icon-class="ion-gear-b"
button-class="link hollow grey-btn"
:button-text="$t('INBOX_MGMT.SETTINGS')"
icon-class="ion-gear-b"
button-class="link hollow grey-btn"
/>
</div>
<!-- <div>
@@ -37,7 +46,7 @@
button-class="link hollow grey-btn"
/>
</div> -->
<div @click="openDelete(item)" v-if="isAdmin()">
<div v-if="isAdmin()" @click="openDelete(item)">
<woot-submit-button
:button-text="$t('INBOX_MGMT.DELETE.BUTTON_TEXT')"
:loading="loading[item.id]"
@@ -57,10 +66,10 @@
</div>
</div>
<settings
v-if="showSettings"
:show.sync="showSettings"
:on-close="closeSettings"
:inbox="selectedInbox"
v-if="showSettings"
/>
<delete-inbox
@@ -78,14 +87,13 @@
/* global bus */
import { mapGetters } from 'vuex';
import InboxListItem from '../../../../components/widgets/InboxListItem';
import Settings from './Settings';
import DeleteInbox from './DeleteInbox';
import adminMixin from '../../../../mixins/isAdmin';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
components: {
InboxListItem,
Settings,
DeleteInbox,
},
@@ -104,13 +112,19 @@ export default {
}),
// Delete Modal
deleteConfirmText() {
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.YES')} ${this.selectedInbox.label}`;
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.YES')} ${
this.selectedInbox.label
}`;
},
deleteRejectText() {
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.NO')} ${this.selectedInbox.label}`;
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.NO')} ${
this.selectedInbox.label
}`;
},
deleteMessage() {
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.MESSAGE')} ${this.selectedInbox.label} ?`;
return `${this.$t('INBOX_MGMT.DELETE.CONFIRM.MESSAGE')} ${
this.selectedInbox.label
} ?`;
},
},
methods: {
@@ -123,9 +137,20 @@ export default {
this.selectedInbox = {};
},
deleteInbox({ channel_id }) {
this.$store.dispatch('deleteInbox', channel_id)
.then(() => bus.$emit('newToastMessage', this.$t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE')))
.catch(() => bus.$emit('newToastMessage', this.$t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE')));
this.$store
.dispatch('deleteInbox', channel_id)
.then(() =>
bus.$emit(
'newToastMessage',
this.$t('INBOX_MGMT.DELETE.API.SUCCESS_MESSAGE')
)
)
.catch(() =>
bus.$emit(
'newToastMessage',
this.$t('INBOX_MGMT.DELETE.API.ERROR_MESSAGE')
)
);
},
confirmDeletion() {
@@ -140,6 +165,7 @@ export default {
this.showDeletePopup = false;
this.selectedInbox = {};
},
frontendURL,
},
};
</script>

View File

@@ -6,11 +6,12 @@ import ChannelList from './ChannelList';
import channelFactory from './channel-factory';
import AddAgents from './AddAgents';
import FinishSetup from './FinishSetup';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/settings/inboxes',
path: frontendURL('settings/inboxes'),
component: SettingsContent,
props: {
headerTitle: 'INBOX_MGMT.HEADER',

View File

@@ -1,10 +1,11 @@
import Index from './Index';
import SettingsContent from '../Wrapper';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/reports',
path: frontendURL('reports'),
component: SettingsContent,
props: {
headerTitle: 'REPORT.HEADER',

View File

@@ -4,18 +4,19 @@ import canned from './canned/canned.routes';
import reports from './reports/reports.routes';
import billing from './billing/billing.routes';
import Auth from '../../../api/auth';
import { frontendURL } from '../../../helper/URLHelper';
export default {
routes: [
{
path: '/u/settings',
path: frontendURL('settings'),
name: 'settings_home',
roles: ['administrator', 'agent'],
redirect: () => {
if (Auth.isAdmin()) {
return '/u/settings/agents/';
return frontendURL('settings/agents');
}
return '/u/settings/canned-response';
return frontendURL('settings/canned-response');
},
},
...inbox.routes,

View File

@@ -5,6 +5,7 @@ import auth from '../api/auth';
import login from './login/login.routes';
import dashboard from './dashboard/dashboard.routes';
import authRoute from './auth/auth.routes';
import { frontendURL } from '../helper/URLHelper';
/* Vue Routes */
const routes = [
@@ -13,7 +14,7 @@ const routes = [
...authRoute.routes,
{
path: '/',
redirect: '/u/dashboard',
redirect: frontendURL('dashboard'),
},
];
@@ -66,15 +67,15 @@ const redirectUser = (to, from, next) => {
const isAccessible =
window.roleWiseRoutes[currentUser.role].indexOf(to.name) > -1;
if (!isAccessible) {
return next('/u/dashboard');
return next(frontendURL('dashboard'));
}
}
// If unprotected and loggedIn -> redirect
if (unProtectedRoutes.indexOf(to.name) !== -1 && isLoggedIn) {
return next('/u/dashboard');
return next(frontendURL('dashboard'));
}
if (unProtectedRoutes.indexOf(to.name) === -1 && !isLoggedIn) {
return next('/u/login');
return next(frontendURL('login'));
}
return next();
};
@@ -82,7 +83,7 @@ const redirectUser = (to, from, next) => {
// protecting routes
router.beforeEach((to, from, next) => {
if (!to.name) {
return next('/u/dashboard');
return next(frontendURL('dashboard'));
}
return redirectUser(to, from, next);

View File

@@ -1,9 +1,10 @@
import Login from './Login';
import { frontendURL } from '../../helper/URLHelper';
export default {
routes: [
{
path: '/u/login',
path: frontendURL('login'),
name: 'login',
component: Login,
},

View File

@@ -7,6 +7,7 @@ import defaultState from '../../i18n/default-sidebar';
import * as types from '../mutation-types';
import Account from '../../api/account';
import ChannelApi from '../../api/channels';
import { frontendURL } from '../../helper/URLHelper';
const state = defaultState;
// inboxes fetch flag
@@ -135,7 +136,7 @@ const mutations = {
payload = payload.map(item => ({
channel_id: item.id,
label: item.name,
toState: `/u/inbox/${item.id}`,
toState: frontendURL(`inbox/${item.id}`),
channelType: item.channelType,
avatarUrl: item.avatar_url,
pageId: item.page_id,
@@ -154,7 +155,7 @@ const mutations = {
menuItems.inbox.children.push({
channel_id: data.id,
label: data.name,
toState: `/u/inbox/${data.id}`,
toState: frontendURL(`inbox/${data.id}`),
channelType: data.channelType,
avatarUrl: data.avatar_url === undefined ? null : data.avatar_url,
pageId: data.page_id,