Move src to dashboard (#152)
11
app/javascript/dashboard/App.Vue.spec.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import App from './App';
|
||||
import '../../test-matchers';
|
||||
|
||||
describe(`App component`, () => {
|
||||
it(`should be a component`, () => {
|
||||
// Arrange
|
||||
// Act
|
||||
expect(App).toBeVueComponent('App');
|
||||
// Assert
|
||||
});
|
||||
});
|
||||
31
app/javascript/dashboard/App.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div id="app" class="app-wrapper app-root">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view></router-view>
|
||||
</transition>
|
||||
<woot-snackbar-box></woot-snackbar-box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootSnackbarBox from './components/SnackbarContainer';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
components: {
|
||||
WootSnackbarBox,
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$store.dispatch('set_user');
|
||||
this.$store.dispatch('validityCheck');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './assets/scss/app';
|
||||
</style>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
||||
142
app/javascript/dashboard/api/account.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getAgents() {
|
||||
const urlData = endPoints('fetchAgents');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addAgent(agentInfo) {
|
||||
const urlData = endPoints('addAgent');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, agentInfo)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
editAgent(agentInfo) {
|
||||
const urlData = endPoints('editAgent')(agentInfo.id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.put(urlData.url, agentInfo)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
deleteAgent(agentId) {
|
||||
const urlData = endPoints('deleteAgent')(agentId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.delete(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getLabels() {
|
||||
const urlData = endPoints('fetchLabels');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
// Get Inbox related to the account
|
||||
getInboxes() {
|
||||
const urlData = endPoints('fetchInboxes');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
deleteInbox(id) {
|
||||
const urlData = endPoints('inbox').delete(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.delete(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
listInboxAgents(id) {
|
||||
const urlData = endPoints('inbox').agents.get(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
updateInboxAgents(inboxId, agentList) {
|
||||
const urlData = endPoints('inbox').agents.post();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, {
|
||||
user_ids: agentList,
|
||||
inbox_id: inboxId,
|
||||
})
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
176
app/javascript/dashboard/api/auth.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint-env browser */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
|
||||
import moment from 'moment';
|
||||
import Cookies from 'js-cookie';
|
||||
import endPoints from './endPoints';
|
||||
import { frontendURL } from '../helper/URLHelper';
|
||||
|
||||
export default {
|
||||
login(creds) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('auth/sign_in', creds)
|
||||
.then(response => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
Cookies.set('user', response.data.data, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
register(creds) {
|
||||
const urlData = endPoints('register');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, {
|
||||
account_name: creds.name,
|
||||
email: creds.email,
|
||||
})
|
||||
.then(response => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
Cookies.set('user', response.data.data, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
validityCheck() {
|
||||
const urlData = endPoints('validityCheck');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response.status === 401) {
|
||||
Cookies.remove('auth_data');
|
||||
Cookies.remove('user');
|
||||
window.location = frontendURL('login');
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
logout() {
|
||||
const urlData = endPoints('logout');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.delete(urlData.url)
|
||||
.then(response => {
|
||||
Cookies.remove('auth_data');
|
||||
Cookies.remove('user');
|
||||
window.location = frontendURL('login');
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
isLoggedIn() {
|
||||
return !!Cookies.getJSON('auth_data');
|
||||
},
|
||||
|
||||
isAdmin() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user').role === 'administrator';
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getAuthData() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('auth_data');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getChannel() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user').channel;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getCurrentUser() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
verifyPasswordToken({ confirmationToken }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post('auth/confirmation', {
|
||||
confirmation_token: confirmationToken,
|
||||
})
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.put('auth/password', {
|
||||
reset_password_token: resetPasswordToken,
|
||||
password_confirmation: confirmPassword,
|
||||
password,
|
||||
})
|
||||
.then(response => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
Cookies.set('user', response.data.data, {
|
||||
expires: expiryDate.diff(moment(), 'days'),
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetPassword({ email }) {
|
||||
const urlData = endPoints('resetPassword');
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, { email })
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
20
app/javascript/dashboard/api/billing.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/* global axios */
|
||||
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getSubscription() {
|
||||
const urlData = endPoints('subscriptions').get();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
112
app/javascript/dashboard/api/cannedResponse.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getAllCannedResponses() {
|
||||
const urlData = endPoints('cannedResponse').get();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
searchCannedResponse({ searchKey }) {
|
||||
let urlData = endPoints('cannedResponse').get();
|
||||
urlData = `${urlData.url}?search=${searchKey}`;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addCannedResponse(cannedResponseObj) {
|
||||
const urlData = endPoints('cannedResponse').post();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, cannedResponseObj)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
editCannedResponse(cannedResponseObj) {
|
||||
const urlData = endPoints('cannedResponse').put(cannedResponseObj.id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.put(urlData.url, cannedResponseObj)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
deleteCannedResponse(responseId) {
|
||||
const urlData = endPoints('cannedResponse').delete(responseId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.delete(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getLabels() {
|
||||
const urlData = endPoints('fetchLabels');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
// Get Inbox related to the account
|
||||
getInboxes() {
|
||||
const urlData = endPoints('fetchInboxes');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
console.log('fetch inboxes success');
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('fetch inboxes failure');
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
56
app/javascript/dashboard/api/channels.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
// Get Inbox related to the account
|
||||
createChannel(channel, channelParams) {
|
||||
const urlData = endPoints('createChannel')(channel, channelParams);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addAgentsToChannel(inboxId, agentsId) {
|
||||
const urlData = endPoints('addAgentsToChannel');
|
||||
urlData.params.inbox_id = inboxId;
|
||||
urlData.params.user_ids = agentsId;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fetchFacebookPages(token) {
|
||||
const urlData = endPoints('fetchFacebookPages');
|
||||
urlData.params.omniauth_token = token;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
187
app/javascript/dashboard/api/endPoints.js
Normal file
@@ -0,0 +1,187 @@
|
||||
/* eslint arrow-body-style: ["error", "always"] */
|
||||
|
||||
const endPoints = {
|
||||
resetPassword: {
|
||||
url: 'auth/password',
|
||||
},
|
||||
register: {
|
||||
url: 'api/v1/accounts.json',
|
||||
},
|
||||
validityCheck: {
|
||||
url: '/auth/validate_token',
|
||||
},
|
||||
logout: {
|
||||
url: 'auth/sign_out',
|
||||
},
|
||||
me: {
|
||||
url: 'api/v1/conversations.json',
|
||||
params: { type: 0, page: 1 },
|
||||
},
|
||||
|
||||
getInbox: {
|
||||
url: 'api/v1/conversations.json',
|
||||
params: { inbox_id: null },
|
||||
},
|
||||
|
||||
conversations(id) {
|
||||
return { url: `api/v1/conversations/${id}.json`, params: { before: null } };
|
||||
},
|
||||
|
||||
resolveConversation(id) {
|
||||
return { url: `api/v1/conversations/${id}/toggle_status.json` };
|
||||
},
|
||||
|
||||
sendMessage(conversationId, message) {
|
||||
return {
|
||||
url: `api/v1/conversations/${conversationId}/messages.json`,
|
||||
params: { message },
|
||||
};
|
||||
},
|
||||
|
||||
addPrivateNote(conversationId, message) {
|
||||
return {
|
||||
url: `api/v1/conversations/${conversationId}/messages.json?`,
|
||||
params: { message, private: 'true' },
|
||||
};
|
||||
},
|
||||
|
||||
fetchLabels: {
|
||||
url: 'api/v1/labels.json',
|
||||
},
|
||||
|
||||
fetchInboxes: {
|
||||
url: 'api/v1/inboxes.json',
|
||||
},
|
||||
|
||||
fetchAgents: {
|
||||
url: 'api/v1/agents.json',
|
||||
},
|
||||
|
||||
addAgent: {
|
||||
url: 'api/v1/agents.json',
|
||||
},
|
||||
|
||||
editAgent(id) {
|
||||
return { url: `api/v1/agents/${id}` };
|
||||
},
|
||||
|
||||
deleteAgent({ id }) {
|
||||
return { url: `api/v1/agents/${id}` };
|
||||
},
|
||||
|
||||
createChannel(channel, channelParams) {
|
||||
return {
|
||||
url: `api/v1/callbacks/register_${channel}_page.json`,
|
||||
params: channelParams,
|
||||
};
|
||||
},
|
||||
|
||||
addAgentsToChannel: {
|
||||
url: 'api/v1/inbox_members.json',
|
||||
params: { user_ids: [], inbox_id: null },
|
||||
},
|
||||
|
||||
fetchFacebookPages: {
|
||||
url: 'api/v1/callbacks/get_facebook_pages.json',
|
||||
params: { omniauth_token: '' },
|
||||
},
|
||||
|
||||
assignAgent(conversationId, AgentId) {
|
||||
return {
|
||||
url: `/api/v1/conversations/${conversationId}/assignments?assignee_id=${AgentId}`,
|
||||
};
|
||||
},
|
||||
|
||||
fbMarkSeen: {
|
||||
url: 'api/v1/facebook_indicators/mark_seen',
|
||||
},
|
||||
|
||||
fbTyping(status) {
|
||||
return {
|
||||
url: `api/v1/facebook_indicators/typing_${status}`,
|
||||
};
|
||||
},
|
||||
|
||||
markMessageRead(id) {
|
||||
return {
|
||||
url: `api/v1/conversations/${id}/update_last_seen`,
|
||||
params: {
|
||||
agent_last_seen_at: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Canned Response [GET, POST, PUT, DELETE]
|
||||
cannedResponse: {
|
||||
get() {
|
||||
return {
|
||||
url: 'api/v1/canned_responses',
|
||||
};
|
||||
},
|
||||
getOne({ id }) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
post() {
|
||||
return {
|
||||
url: 'api/v1/canned_responses',
|
||||
};
|
||||
},
|
||||
put(id) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
delete(id) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
reports: {
|
||||
account(metric, from, to) {
|
||||
return {
|
||||
url: `/api/v1/reports/account?metric=${metric}&since=${from}&to=${to}`,
|
||||
};
|
||||
},
|
||||
accountSummary(accountId, from, to) {
|
||||
return {
|
||||
url: `/api/v1/reports/${accountId}/account_summary?since=${from}&to=${to}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
subscriptions: {
|
||||
get() {
|
||||
return {
|
||||
url: '/api/v1/subscriptions',
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
inbox: {
|
||||
delete(id) {
|
||||
return {
|
||||
url: `/api/v1/inboxes/${id}`,
|
||||
};
|
||||
},
|
||||
agents: {
|
||||
get(id) {
|
||||
return {
|
||||
url: `/api/v1/inbox_members/${id}.json`,
|
||||
};
|
||||
},
|
||||
post() {
|
||||
return {
|
||||
url: '/api/v1/inbox_members.json',
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default page => {
|
||||
return endPoints[page];
|
||||
};
|
||||
104
app/javascript/dashboard/api/inbox/conversation.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
fetchConversation(id) {
|
||||
const urlData = endPoints('conversations')(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
toggleStatus(id) {
|
||||
const urlData = endPoints('resolveConversation')(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
assignAgent([id, agentId]) {
|
||||
const urlData = endPoints('assignAgent')(id, agentId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
markSeen({ inboxId, senderId }) {
|
||||
const urlData = endPoints('fbMarkSeen');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, {
|
||||
inbox_id: inboxId,
|
||||
sender_id: senderId,
|
||||
})
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fbTyping({ flag, inboxId, senderId }) {
|
||||
const urlData = endPoints('fbTyping')(flag);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, {
|
||||
inbox_id: inboxId,
|
||||
sender_id: senderId,
|
||||
})
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
markMessageRead({ id, lastSeen }) {
|
||||
const urlData = endPoints('markMessageRead')(id);
|
||||
urlData.params.agent_last_seen_at = lastSeen;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
32
app/javascript/dashboard/api/inbox/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
fetchAllConversations(params, callback) {
|
||||
const urlData = endPoints('getInbox');
|
||||
|
||||
if (params.inbox !== 0) {
|
||||
urlData.params.inbox_id = params.inbox;
|
||||
} else {
|
||||
urlData.params.inbox_id = null;
|
||||
}
|
||||
urlData.params = {
|
||||
...urlData.params,
|
||||
conversation_status_id: params.convStatus,
|
||||
assignee_type_id: params.assigneeStatus,
|
||||
};
|
||||
axios
|
||||
.get(urlData.url, {
|
||||
params: urlData.params,
|
||||
})
|
||||
.then(response => {
|
||||
callback(response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
};
|
||||
55
app/javascript/dashboard/api/inbox/message.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
sendMessage([conversationId, message]) {
|
||||
const urlData = endPoints('sendMessage')(conversationId, message);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addPrivateNote([conversationId, message]) {
|
||||
const urlData = endPoints('addPrivateNote')(conversationId, message);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(urlData.url, urlData.params)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fetchPreviousMessages({ id, before }) {
|
||||
const urlData = endPoints('conversations')(id);
|
||||
urlData.params.before = before;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url, {
|
||||
params: urlData.params,
|
||||
})
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
34
app/javascript/dashboard/api/reports.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/* global axios */
|
||||
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getAccountReports(metric, from, to) {
|
||||
const urlData = endPoints('reports').account(metric, from, to);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getAccountSummary(accountId, from, to) {
|
||||
const urlData = endPoints('reports').accountSummary(accountId, from, to);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(urlData.url)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
BIN
app/javascript/dashboard/assets/audio/ding.mp3
Normal file
25
app/javascript/dashboard/assets/images/agent.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/javascript/dashboard/assets/images/bottom-nav.png
Normal file
|
After Width: | Height: | Size: 130 B |
13
app/javascript/dashboard/assets/images/canned.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="504px" height="470px" viewBox="0 0 504 470" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44 (41411) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>canned</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="canned" fill-rule="nonzero">
|
||||
<path d="M329.386,362.733 L39.253,362.733 C20.48,362.733 5.12,347.373 5.12,328.6 L5.12,38.467 C5.12,19.694 20.48,4.334 39.253,4.334 L465.92,4.334 C484.693,4.334 500.053,19.694 500.053,38.467 L500.053,328.6 C500.053,347.373 484.693,362.733 465.92,362.733 L431.787,362.733 L431.787,465.133 L329.386,362.733 Z" id="Shape" fill="#B9ECFF"></path>
|
||||
<path d="M431.786,469.4 C430.933,469.4 429.226,468.547 428.373,468.547 L308.907,349.08 C307.2,347.373 307.2,344.813 308.907,343.107 C310.614,341.4 313.174,341.4 314.88,343.107 L426.667,454.894 L426.667,362.734 C426.667,360.174 428.374,358.467 430.934,358.467 L465.067,358.467 C481.28,358.467 494.934,344.814 494.934,328.6 L494.934,38.467 C494.934,22.254 481.281,8.6 465.067,8.6 L38.4,8.6 C22.187,8.6 8.533,22.253 8.533,38.467 L8.533,328.6 C8.533,344.813 22.186,358.467 38.4,358.467 L285.867,358.467 C288.427,358.467 290.134,360.174 290.134,362.734 C290.134,365.294 288.427,367.001 285.867,367.001 L38.4,367.001 C17.067,367 0,349.933 0,328.6 L0,38.467 C0,17.134 17.067,0.067 38.4,0.067 L465.067,0.067 C486.4,0.067 503.467,17.134 503.467,38.467 L503.467,328.6 C503.467,349.933 486.4,367 465.067,367 L435.2,367 L435.2,465.133 C435.2,466.84 434.347,468.546 432.64,469.4 C432.64,469.4 432.64,469.4 431.786,469.4 Z M397.653,264.6 L295.253,264.6 C292.693,264.6 290.986,262.893 290.986,260.333 C290.986,257.773 292.693,256.066 295.253,256.066 L397.653,256.066 C400.213,256.066 401.92,257.773 401.92,260.333 C401.92,262.893 400.213,264.6 397.653,264.6 Z M261.12,264.6 L107.52,264.6 C104.96,264.6 103.253,262.893 103.253,260.333 C103.253,257.773 104.96,256.066 107.52,256.066 L261.12,256.066 C263.68,256.066 265.387,257.773 265.387,260.333 C265.387,262.893 263.68,264.6 261.12,264.6 Z M380.586,213.4 L278.186,213.4 C275.626,213.4 273.919,211.693 273.919,209.133 C273.919,206.573 275.626,204.866 278.186,204.866 L380.586,204.866 C383.146,204.866 384.853,206.573 384.853,209.133 C384.853,211.693 383.147,213.4 380.586,213.4 Z M244.053,213.4 L107.52,213.4 C104.96,213.4 103.253,211.693 103.253,209.133 C103.253,206.573 104.96,204.866 107.52,204.866 L244.053,204.866 C246.613,204.866 248.32,206.573 248.32,209.133 C248.32,211.693 246.613,213.4 244.053,213.4 Z M397.653,162.2 L346.453,162.2 C343.893,162.2 342.186,160.493 342.186,157.933 C342.186,155.373 343.893,153.666 346.453,153.666 L397.653,153.666 C400.213,153.666 401.92,155.373 401.92,157.933 C401.92,160.493 400.213,162.2 397.653,162.2 Z M312.32,162.2 L244.053,162.2 C241.493,162.2 239.786,160.493 239.786,157.933 C239.786,155.373 241.493,153.666 244.053,153.666 L312.32,153.666 C314.88,153.666 316.587,155.373 316.587,157.933 C316.586,160.493 314.88,162.2 312.32,162.2 Z M209.92,162.2 L107.52,162.2 C104.96,162.2 103.253,160.493 103.253,157.933 C103.253,155.373 104.96,153.666 107.52,153.666 L209.92,153.666 C212.48,153.666 214.187,155.373 214.187,157.933 C214.186,160.493 212.48,162.2 209.92,162.2 Z M380.586,111 L209.92,111 C207.36,111 205.653,109.293 205.653,106.733 C205.653,104.173 207.36,102.466 209.92,102.466 L380.587,102.466 C383.147,102.466 384.854,104.173 384.854,106.733 C384.853,109.293 383.147,111 380.586,111 Z M175.786,111 L107.52,111 C104.96,111 103.253,109.293 103.253,106.733 C103.253,104.173 104.96,102.466 107.52,102.466 L175.787,102.466 C178.347,102.466 180.054,104.173 180.054,106.733 C180.053,109.293 178.346,111 175.786,111 Z" id="Shape" fill="#6C6C6C"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/javascript/dashboard/assets/images/channels/facebook.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
app/javascript/dashboard/assets/images/channels/line.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/javascript/dashboard/assets/images/channels/telegram.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
app/javascript/dashboard/assets/images/channels/twitter.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
19
app/javascript/dashboard/assets/images/chat.svg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
app/javascript/dashboard/assets/images/fb-badge.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
25
app/javascript/dashboard/assets/images/inboxes.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="504px" height="504px" viewBox="0 0 504 504" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44 (41411) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>email</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="email" fill-rule="nonzero">
|
||||
<g id="Group" transform="translate(3.000000, 4.000000)">
|
||||
<path d="M427.9,230.65 L427.9,230.65 L311,320.25 L274.3,348.45 C274.3,348.45 261.5,359.55 248.7,359.55 C235.9,359.55 223.1,348.45 223.1,348.45 L185.6,320.25 L69.5,230.65 L69.5,230.65 L69.5,111.25 L69.5,34.45 C69.5,16.55 84.9,0.35 103.6,0.35 L393.7,0.35 C411.6,0.35 427.8,15.75 427.8,34.45 L427.8,111.25 L427.8,230.65 L427.9,230.65 Z" id="Shape" fill="#ECF4F7"></path>
|
||||
<g transform="translate(2.000000, 111.000000)" id="SVGCleanerId_0" fill="#AAB1BA">
|
||||
<path d="M67.5,0.25 L67.5,119.75 L67.5,119.75 L0.9,69.35 L0.1,69.35 C1.8,51.45 10.3,34.35 25.7,25.85 L67.5,0.25 Z M494.2,70.15 L491.6,70.15 L425.9,119.65 L425.9,119.65 L425.9,0.25 L468.6,25.85 C483.9,35.25 492.5,51.45 494.2,70.15 Z"></path>
|
||||
</g>
|
||||
<g transform="translate(2.000000, 111.000000)" id="SVGCleanerId_0_1_" fill="#AAB1BA">
|
||||
<path d="M67.5,0.25 L67.5,119.75 L67.5,119.75 L0.9,69.35 L0.1,69.35 C1.8,51.45 10.3,34.35 25.7,25.85 L67.5,0.25 Z M494.2,70.15 L491.6,70.15 L425.9,119.65 L425.9,119.65 L425.9,0.25 L468.6,25.85 C483.9,35.25 492.5,51.45 494.2,70.15 Z"></path>
|
||||
</g>
|
||||
<path d="M470.6,136.85 L427.9,111.25 L427.9,230.75 L311.8,319.45 L275.1,347.65 C275.1,347.65 262.3,358.75 249.5,358.75 C236.7,358.75 223.9,347.65 223.9,347.65 L194.3,325.45 L185.5,318.65 L69.5,230.65 L69.5,111.25 L27.7,136.85 C12.4,145.35 3.9,162.25 2.1,180.05 L0.4,179.45 C0.4,182.05 0.4,184.55 0.4,187.15 L0.4,460.25 C0.4,463.65 1.3,467.05 2.1,470.45 C6.4,484.95 20,495.15 35.4,495.15 L462.1,495.15 C478.3,495.15 492,484.05 495.4,469.55 C496.3,466.95 496.3,463.55 496.3,461.05 L496.3,188.05 C496.3,185.45 496.3,183.75 496.3,181.25 C494.5,162.45 485.9,146.25 470.6,136.85 Z" id="Shape" fill="#FFD0A1"></path>
|
||||
<g transform="translate(2.000000, 111.000000)" id="SVGCleanerId_0_2_" fill="#AAB1BA">
|
||||
<path d="M67.5,0.25 L67.5,119.75 L67.5,119.75 L0.9,69.35 L0.1,69.35 C1.8,51.45 10.3,34.35 25.7,25.85 L67.5,0.25 Z M494.2,70.15 L491.6,70.15 L425.9,119.65 L425.9,119.65 L425.9,0.25 L468.6,25.85 C483.9,35.25 492.5,51.45 494.2,70.15 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M465,503.45 L38.4,503.45 C17.1,503.45 0,486.35 0,465.05 L0,192.05 C0,169.05 11.1,146.85 27.3,137.45 L35.8,131.45 C37.5,129.75 40.1,130.55 41.8,132.35 C43.5,134.15 42.7,136.65 40.9,138.35 L32.4,144.35 C16.2,153.75 8.5,175.05 8.5,192.15 L8.5,465.25 C8.5,482.35 21.3,495.15 38.4,495.15 L465.1,495.15 C482.2,495.15 495,482.35 495,465.25 L495,192.05 C495,170.75 486.5,153.65 471.1,144.25 L462.6,138.25 C460.9,136.55 460,133.95 461.7,132.25 C463.4,130.55 466,129.65 467.7,131.35 L476.2,137.35 C493.3,148.45 503.5,168.05 503.5,191.95 L503.5,465.05 C503.4,486.45 486.4,503.45 465,503.45 Z M465,452.25 C464.1,452.25 463.3,452.25 462.4,451.35 L342.9,357.45 C341.2,355.75 340.3,353.15 342,351.45 C343.7,349.75 346.3,348.85 348,350.55 L467.5,444.45 C469.2,446.15 470.1,448.75 468.4,450.45 C467.6,451.45 465.9,452.25 465,452.25 Z M38.4,452.25 C37.5,452.25 35.8,451.35 35,450.55 C33.3,448.85 34.1,446.25 35.9,444.55 L155.3,350.75 C157,349.05 159.6,349.85 161.3,351.65 C163,353.35 162.2,355.95 160.4,357.65 L40.9,451.45 C40.1,452.25 39.2,452.25 38.4,452.25 Z M251.7,366.95 C238,366.95 223.5,355.05 223.5,355.05 L36.7,212.45 C35,210.75 34.1,208.15 35.8,206.45 C37.5,204.75 40.1,203.85 41.8,205.55 L69.1,226.05 L69.1,38.45 C69.1,17.95 87,0.05 107.5,0.05 L397.6,0.05 C418.1,0.05 436,17.95 436,38.45 L436,226.15 L463.3,205.65 C465,203.95 467.6,204.75 469.3,206.55 C471,208.35 470.2,210.85 468.4,212.55 L280.7,355.05 C279,355.85 265.4,366.95 251.7,366.95 Z M76.8,232.95 L228.7,348.15 C228.7,348.15 240.6,358.35 251.7,358.35 C262.8,358.35 274.7,348.95 274.7,348.95 L426.6,232.85 L426.6,38.45 C426.6,22.25 412.9,8.55 396.7,8.55 L106.6,8.55 C90.4,8.55 76.7,22.25 76.7,38.45 L76.7,232.95 L76.8,232.95 Z M294.4,298.65 L209.1,298.65 C206.5,298.65 204.8,296.95 204.8,294.35 C204.8,291.75 206.5,290.05 209.1,290.05 L294.4,290.05 C297,290.05 298.7,291.75 298.7,294.35 C298.6,296.95 296.9,298.65 294.4,298.65 Z M345.6,247.45 L311.5,247.45 C308.9,247.45 307.2,245.75 307.2,243.15 C307.2,240.55 308.9,238.85 311.5,238.85 L345.6,238.85 C348.2,238.85 349.9,240.55 349.9,243.15 C349.9,245.75 348.1,247.45 345.6,247.45 Z M277.3,247.45 L157.8,247.45 C155.2,247.45 153.5,245.75 153.5,243.15 C153.5,240.55 155.2,238.85 157.8,238.85 L277.3,238.85 C279.9,238.85 281.6,240.55 281.6,243.15 C281.6,245.75 279.9,247.45 277.3,247.45 Z M345.6,196.25 L243.2,196.25 C240.6,196.25 238.9,194.55 238.9,191.95 C238.9,189.35 240.6,187.65 243.2,187.65 L345.6,187.65 C348.2,187.65 349.9,189.35 349.9,191.95 C349.8,194.55 348.1,196.25 345.6,196.25 Z M209,196.25 L157.8,196.25 C155.2,196.25 153.5,194.55 153.5,191.95 C153.5,189.35 155.2,187.65 157.8,187.65 L209,187.65 C211.6,187.65 213.3,189.35 213.3,191.95 C213.3,194.55 211.6,196.25 209,196.25 Z M260.2,128.05 L157.8,128.05 C155.2,128.05 153.5,126.35 153.5,123.75 C153.5,121.15 155.2,119.45 157.8,119.45 L260.2,119.45 C262.8,119.45 264.5,121.15 264.5,123.75 C264.5,126.35 262.8,128.05 260.2,128.05 Z" id="Shape" fill="#51565F"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
71
app/javascript/dashboard/assets/images/lock.svg
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 503.467 503.467" style="enable-background:new 0 0 503.467 503.467;" xml:space="preserve">
|
||||
<g transform="translate(5 1)">
|
||||
<polygon style="fill:#F2EDDA;" points="408.867,105.667 169.933,105.667 118.733,105.667 118.733,156.867 135.8,165.4
|
||||
118.733,173.933 118.733,327.533 135.8,319 144.333,344.6 374.733,344.6 383.267,327.533 391.8,344.6 460.067,344.6
|
||||
460.067,310.467 425.933,293.4 460.067,276.333 460.067,105.667 "/>
|
||||
<path style="fill:#AE938D;" d="M67.533,54.467V498.2H33.4V20.333c0-9.387,7.68-17.067,17.067-17.067s17.067,7.68,17.067,17.067
|
||||
V54.467z"/>
|
||||
</g>
|
||||
<path style="fill:#51565F;" d="M499.2,503.467H362.667c-2.56,0-4.267-1.707-4.267-4.267c0-2.56,1.707-4.267,4.267-4.267H499.2
|
||||
c2.56,0,4.267,1.707,4.267,4.267C503.467,501.76,501.76,503.467,499.2,503.467z M328.533,503.467h-17.067
|
||||
c-2.56,0-4.267-1.707-4.267-4.267c0-2.56,1.707-4.267,4.267-4.267h17.067c2.56,0,4.267,1.707,4.267,4.267
|
||||
C332.8,501.76,331.093,503.467,328.533,503.467z M260.267,503.467h-85.333c-2.56,0-4.267-1.707-4.267-4.267
|
||||
c0-2.56,1.707-4.267,4.267-4.267h85.333c2.56,0,4.267,1.707,4.267,4.267C264.533,501.76,262.827,503.467,260.267,503.467z
|
||||
M123.733,503.467H4.267C1.707,503.467,0,501.76,0,499.2c0-2.56,1.707-4.267,4.267-4.267h29.867v-473.6
|
||||
C34.133,9.387,43.52,0,55.467,0S76.8,9.387,76.8,21.333V51.2h422.4c2.56,0,4.267,1.707,4.267,4.267c0,2.56-1.707,4.267-4.267,4.267
|
||||
h-81.067V102.4h46.933c2.56,0,4.267,1.707,4.267,4.267v170.667c0,1.707-0.853,3.413-2.56,3.413L440.32,294.4l26.453,13.653
|
||||
c1.707,0.853,2.56,2.56,2.56,3.413V345.6c0,2.56-1.707,4.267-4.267,4.267H396.8c-1.707,0-3.413-0.853-3.413-2.56l-5.12-9.387
|
||||
l-5.12,9.387c-0.853,1.707-2.56,2.56-3.413,2.56h-230.4c-1.707,0-3.413-0.853-4.267-2.56l-6.827-21.333l-12.8,5.973
|
||||
c-1.707,0.853-2.56,0.853-4.267,0c-0.853-0.853-1.707-2.56-1.707-3.413v-153.6c0-1.707,0.853-3.413,2.56-3.413l9.387-5.12
|
||||
l-9.387-5.12c-1.707-0.853-2.56-2.56-2.56-3.413v-51.2c0-2.56,1.707-4.267,4.267-4.267h46.933V59.734H72.533
|
||||
c-2.56,0-4.267-1.707-4.267-4.267V21.334c0-6.827-5.973-12.8-12.8-12.8s-12.8,5.973-12.8,12.8v473.6h25.6V89.6
|
||||
c0-2.56,1.707-4.267,4.267-4.267c2.56,0,4.267,1.707,4.267,4.267v405.333h46.933c2.56,0,4.267,1.707,4.267,4.267
|
||||
C128,501.76,126.293,503.467,123.733,503.467z M399.36,341.333h61.44v-27.307l-31.573-16.213c-1.707-0.853-2.56-2.56-2.56-3.413
|
||||
c0-0.853,0.853-3.413,2.56-3.413l31.573-16.213v-163.84h-46.933c-2.56,0-4.267-1.707-4.267-4.267V59.733H179.2v46.933
|
||||
c0,2.56-1.707,4.267-4.267,4.267H128v44.373l14.507,7.68c1.707,0.853,2.56,2.56,2.56,3.413c0,0.853-0.853,3.413-2.56,3.413
|
||||
L128,177.493v144.213l11.093-5.12c0.853-0.853,2.56-0.853,3.413,0c0.853,0.853,1.707,1.707,2.56,2.56l7.68,23.04h224.427
|
||||
l7.68-14.507c1.707-2.56,5.973-2.56,7.68,0L399.36,341.333z M260.267,281.6H192c-2.56,0-4.267-1.707-4.267-4.267
|
||||
s1.707-4.267,4.267-4.267h68.267c2.56,0,4.267,1.707,4.267,4.267S262.827,281.6,260.267,281.6z M362.667,264.533
|
||||
c-0.853,0-2.56,0-3.413-0.853c-1.707-1.707-1.707-4.267,0-5.973l26.453-26.453H327.68c-2.56,0-4.267-1.707-4.267-4.267
|
||||
c0-2.56,1.707-4.267,4.267-4.267h58.027l-26.453-26.453c-1.707-1.707-1.707-4.267,0-5.973c1.707-1.707,4.267-1.707,5.973,0
|
||||
l34.133,34.133l0,0l0,0l0,0l0,0l0,0c0,0,0.853,0.853,0.853,1.707c0,0.853,0,0.853,0,1.707l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0l0,0
|
||||
c0,0.853,0,0.853,0,1.707c0,0.853-0.853,0.853-0.853,1.707l0,0l0,0l0,0l0,0l0,0l-34.133,34.133
|
||||
C365.227,264.533,363.52,264.533,362.667,264.533z M277.333,230.4H192c-2.56,0-4.267-1.707-4.267-4.267s1.707-4.267,4.267-4.267
|
||||
h85.333c2.56,0,4.267,1.707,4.267,4.267S279.893,230.4,277.333,230.4z M243.2,179.2H192c-2.56,0-4.267-1.707-4.267-4.267
|
||||
c0-2.56,1.707-4.267,4.267-4.267h51.2c2.56,0,4.267,1.707,4.267,4.267C247.467,177.493,245.76,179.2,243.2,179.2z M379.733,110.933
|
||||
H209.067c-2.56,0-4.267-1.707-4.267-4.267s1.707-4.267,4.267-4.267h170.667c2.56,0,4.267,1.707,4.267,4.267
|
||||
S382.293,110.933,379.733,110.933z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
22
app/javascript/dashboard/assets/images/no-inboxes.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="504px" height="504px" viewBox="0 0 504 504" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44 (41411) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>billboard</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="billboard">
|
||||
<g id="Layer_1" fill-rule="nonzero">
|
||||
<g id="Group" transform="translate(4.000000, 4.000000)">
|
||||
<path d="M95.84,325.387 C100.107,342.454 102.667,359.52 102.667,375.734 C102.667,405.601 81.334,426.934 51.467,426.934 C22.454,426.934 0.267,404.747 0.267,375.734 C0.267,324.534 25.867,256.267 51.467,256.267 C69.387,256.267 86.453,288.693 95.84,325.387 Z M478.133,461.067 C487.52,461.067 495.2,468.747 495.2,478.134 C495.2,487.521 487.52,495.201 478.133,495.201 L409.866,495.201 C400.479,495.201 392.799,487.521 392.799,478.134 C392.799,468.747 400.479,461.067 409.866,461.067 C413.279,461.067 416.693,461.92 419.253,463.627 C421.813,452.533 432.053,444 444,444 C455.947,444 466.187,452.533 468.747,463.627 C471.307,461.92 474.72,461.067 478.133,461.067 Z" id="Shape" fill="#50DD8E"></path>
|
||||
<path d="M392.8,478.133 L392.8,495.2 L358.667,495.2 L358.667,324.533 L392.8,324.533 L392.8,478.133 Z M102.667,375.733 C102.667,359.52 100.107,341.6 95.84,325.386 L98.4,324.533 L136.8,324.533 L136.8,495.2 L102.667,495.2 L102.667,375.733 Z" id="Shape" fill="#AAB1BA"></path>
|
||||
<path d="M461.066,0.267 C479.839,0.267 495.199,15.627 495.199,34.4 L495.199,290.4 C495.199,309.173 479.839,324.533 461.066,324.533 L392.8,324.533 L358.667,324.533 L136.8,324.533 L98.4,324.533 L95.84,325.386 C86.453,288.693 68.533,256.266 51.467,256.266 C25.867,256.266 0.267,324.533 0.267,375.733 L0.267,34.4 C0.267,15.627 15.627,0.267 34.4,0.267 L119.733,0.267 L375.733,0.267 L461.066,0.267 Z" id="Shape" fill="#B9ECFF"></path>
|
||||
</g>
|
||||
<path d="M482.133,503.467 C479.573,503.467 477.866,501.76 477.866,499.2 C477.866,496.64 479.573,494.933 482.133,494.933 C488.96,494.933 494.933,488.96 494.933,482.133 C494.933,475.306 488.96,469.333 482.133,469.333 C479.573,469.333 477.013,470.186 475.306,471.04 C474.453,471.893 472.746,471.893 471.039,471.04 C469.332,470.187 468.479,469.333 468.479,467.627 C465.919,458.24 457.386,451.414 447.999,451.414 C438.612,451.414 429.226,458.241 427.519,467.627 C427.519,469.334 426.666,470.187 424.959,471.04 C423.252,471.893 422.399,471.04 420.692,471.04 C418.985,469.333 416.425,469.333 413.865,469.333 C407.038,469.333 401.065,475.306 401.065,482.133 C401.065,488.96 407.038,494.933 413.865,494.933 C416.425,494.933 418.132,496.64 418.132,499.2 C418.132,501.76 416.425,503.467 413.865,503.467 L4.267,503.467 C1.707,503.467 0,501.76 0,499.2 C0,496.64 1.707,494.933 4.267,494.933 L51.2,494.933 L51.2,435.2 C22.187,433.493 0,409.6 0,379.733 C0,331.093 24.747,256 55.467,256 C86.187,256 110.934,331.093 110.934,379.733 C110.934,410.453 89.601,433.493 59.734,435.2 L59.734,494.933 L102.4,494.933 L102.4,456.533 C102.4,453.973 104.107,452.266 106.667,452.266 C109.227,452.266 110.934,453.973 110.934,456.533 L110.934,494.933 L136.534,494.933 L136.534,362.667 C136.534,360.107 138.241,358.4 140.801,358.4 C143.361,358.4 145.068,360.107 145.068,362.667 L145.068,494.934 L358.4,494.934 L358.4,362.667 C358.4,360.107 360.107,358.4 362.667,358.4 C365.227,358.4 366.934,360.107 366.934,362.667 L366.934,494.934 L392.534,494.934 L392.534,362.667 C392.534,360.107 394.241,358.4 396.801,358.4 C399.361,358.4 401.068,360.107 401.068,362.667 L401.068,465.067 C406.188,460.8 413.868,459.947 420.695,461.654 C424.962,450.561 436.055,443.734 448.002,443.734 C459.949,443.734 471.042,450.561 475.309,461.654 C477.869,460.801 479.576,460.801 482.136,460.801 C494.083,460.801 503.469,470.188 503.469,482.134 C503.469,494.08 494.08,503.467 482.133,503.467 Z M55.467,264.533 C34.134,264.533 8.534,327.68 8.534,379.733 C8.534,406.186 29.014,426.666 55.467,426.666 C82.774,426.666 102.4,407.039 102.4,379.733 C102.4,327.68 76.8,264.533 55.467,264.533 Z M46.933,392.533 C40.106,392.533 34.133,386.56 34.133,379.733 C34.133,377.173 35.84,375.466 38.4,375.466 C40.96,375.466 42.667,377.173 42.667,379.733 C42.667,382.293 44.374,384 46.934,384 C49.494,384 51.201,385.707 51.201,388.267 C51.2,390.827 49.493,392.533 46.933,392.533 Z M72.533,375.467 C69.973,375.467 68.266,373.76 68.266,371.2 C68.266,368.64 66.559,366.933 63.999,366.933 C61.439,366.933 59.732,365.226 59.732,362.666 C59.732,360.106 61.44,358.4 64,358.4 C70.827,358.4 76.8,364.373 76.8,371.2 C76.8,373.76 75.093,375.467 72.533,375.467 Z M465.067,332.8 L132.267,332.8 C129.707,332.8 128,331.093 128,328.533 C128,325.973 129.707,324.266 132.267,324.266 L465.067,324.266 C481.28,324.266 494.934,310.613 494.934,294.399 L494.934,38.399 C494.934,22.186 481.281,8.532 465.067,8.532 L384,8.532 L384,34.132 L413.867,34.132 C416.427,34.132 418.134,35.839 418.134,38.399 C418.134,40.959 416.427,42.666 413.867,42.666 L345.6,42.666 C343.04,42.666 341.333,40.959 341.333,38.399 C341.333,35.839 343.04,34.132 345.6,34.132 L375.467,34.132 L375.467,8.532 L128,8.532 L128,34.132 L157.867,34.132 C160.427,34.132 162.134,35.839 162.134,38.399 C162.134,40.959 160.427,42.666 157.867,42.666 L89.6,42.666 C87.04,42.666 85.333,40.959 85.333,38.399 C85.333,35.839 87.04,34.132 89.6,34.132 L119.467,34.132 L119.467,8.532 L38.4,8.532 C22.187,8.533 8.533,22.187 8.533,38.4 L8.533,260.267 C8.533,262.827 6.826,264.534 4.266,264.534 C1.706,264.534 0,262.827 0,260.267 L0,38.4 C0,17.067 17.067,0 38.4,0 L465.067,0 C486.4,0 503.467,17.067 503.467,38.4 L503.467,294.4 C503.467,315.733 486.4,332.8 465.067,332.8 Z M46.933,332.8 C44.373,332.8 42.666,331.093 42.666,328.533 C42.666,321.706 48.639,315.733 55.466,315.733 C58.026,315.733 59.733,317.44 59.733,320 C59.733,322.56 58.026,324.267 55.466,324.267 C52.906,324.267 51.199,325.974 51.199,328.534 C51.2,331.093 49.493,332.8 46.933,332.8 Z" id="Shape" fill="#6C6C6C"></path>
|
||||
</g>
|
||||
<text id="NO-INBOXES" font-family="HoboStd, Hobo Std" font-size="64" font-weight="400" fill="#6C6C6C">
|
||||
<tspan x="60" y="195">NO INBOXES</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/javascript/dashboard/assets/images/no_page_image.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
app/javascript/dashboard/assets/images/woot-logo.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
24
app/javascript/dashboard/assets/images/woot-logo.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1405px" height="297px" viewBox="0 0 1405 297" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group 2</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-2" transform="translate(-65.000000, -3.000000)">
|
||||
<g id="Group" transform="translate(65.000000, 56.000000)" fill="#273444">
|
||||
<path d="M199.995615,244 L99.9912292,244 C44.8625181,244 0,199.133097 0,143.991229 C0,88.8669035 44.8625181,44 99.9912292,44 C155.133097,44 199.995615,88.8669035 199.995615,143.991229 L199.995615,244 Z M99.7834365,58 C52.4804488,58 14,96.4804488 14,143.783437 C14,191.086424 52.4804488,229.580002 99.7834365,229.580002 L185.580002,229.580002 L185.580002,143.783437 C185.580002,96.4804488 147.082048,58 99.7834365,58 Z" id="Fill-1"></path>
|
||||
<text id="chatwoot" font-family="AvenirNext-Regular, Avenir Next" font-size="240" font-weight="normal">
|
||||
<tspan x="234" y="240">chatwoot</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<rect id="Rectangle" x="0" y="40" width="1377" height="360"></rect>
|
||||
<g id="Group-3" transform="translate(1310.000000, 0.000000)">
|
||||
<rect id="Rectangle-2" fill="#47A7F6" x="0" y="3" width="160" height="160" rx="80"></rect>
|
||||
<text id="β" font-family="AvenirNext-DemiBold, Avenir Next" font-size="90" font-weight="500" fill="#FFFFFF">
|
||||
<tspan x="51" y="111">β</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
87
app/javascript/dashboard/assets/scss/_animations.scss
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
/* Enter and leave animations can use different */
|
||||
/* durations and timing functions. */
|
||||
.slide-fade-enter-active {
|
||||
@include transition(all .3s $ease-in-cubic);
|
||||
}
|
||||
.slide-fade-leave-active {
|
||||
@include transition(all .3s $ease-out-cubic);
|
||||
}
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.slide-fade-enter {
|
||||
transform: translateX($space-micro);
|
||||
}
|
||||
.slide-fade-leave-to {
|
||||
transform: translateX($space-medium);
|
||||
}
|
||||
|
||||
.conversations-list-enter-active, .conversations-list-leave-active {
|
||||
@include transition(all .25s $ease-out-cubic);
|
||||
}
|
||||
.conversations-list-enter, .conversations-list-leave-to /* .conversations-list-leave-active for <2.1.8 */ {
|
||||
opacity: 0;
|
||||
transform: translateX($space-medium);
|
||||
}
|
||||
|
||||
.menu-list-enter-active, .menu-list-leave-active {
|
||||
@include transition(all .2s $ease-out-cubic);
|
||||
}
|
||||
.menu-list-enter, .menu-list-leave-to /* .conversations-list-leave-active for <2.1.8 */ {
|
||||
opacity: 0;
|
||||
transform: translateX($space-medium);
|
||||
}
|
||||
|
||||
.slide-up-enter-active {
|
||||
@include transition(all .3s $ease-in-cubic);
|
||||
}
|
||||
|
||||
.slide-up-leave-active {
|
||||
@include transition(all .3s $ease-out-cubic);
|
||||
}
|
||||
|
||||
.slide-up-enter, .slide-up-leave-to
|
||||
/* .slide-fade-leave-active for <2.1.8 */ {
|
||||
transform: translateY(-$space-medium);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.menu-slide-enter-active, .menu-slide-leave-active {
|
||||
@include transition(all .15s $ease-in-cubic);
|
||||
}
|
||||
|
||||
.menu-slide-enter, .menu-slide-leave-to
|
||||
/* .slide-fade-leave-active for <2.1.8 */ {
|
||||
@include transform(translateY($space-small));
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.toast-fade-enter-active {
|
||||
@include transition(all .3s $ease-in-sine);
|
||||
}
|
||||
.toast-fade-leave-active {
|
||||
@include transition(all .1s $ease-out-sine);
|
||||
}
|
||||
.toast-fade-enter, .toast-fade-leave-to
|
||||
/* .toast-fade-leave-active for <2.1.8 */ {
|
||||
@include transform(translateY(-$space-small));
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.modal-fade-enter-active {
|
||||
@include transition(all .3s $ease-in-sine);
|
||||
}
|
||||
|
||||
.modal-fade-leave-active {
|
||||
@include transition(all .1s $ease-out-sine);
|
||||
}
|
||||
|
||||
.modal-fade-enter, .modal-fade-leave-to
|
||||
/* .slide-fade-leave-active for <2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
27
app/javascript/dashboard/assets/scss/_foundation-custom.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
.button {
|
||||
font-weight: $font-weight-medium;
|
||||
font-family: $body-font-family;
|
||||
|
||||
&.round {
|
||||
border-radius: 1000px;
|
||||
}
|
||||
|
||||
&.grey-btn {
|
||||
color: $color-gray;
|
||||
|
||||
&:hover {
|
||||
color: $color-light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
max-width: 15rem;
|
||||
padding: $space-smaller $space-small;
|
||||
border-radius: $space-smaller;
|
||||
font-size: $font-size-mini;
|
||||
}
|
||||
649
app/javascript/dashboard/assets/scss/_foundation-settings.scss
Normal file
@@ -0,0 +1,649 @@
|
||||
// Foundation for Sites Settings
|
||||
// -----------------------------
|
||||
//
|
||||
// Table of Contents:
|
||||
//
|
||||
// 1. Global
|
||||
// 2. Breakpoints
|
||||
// 3. The Grid
|
||||
// 4. Base Typography
|
||||
// 5. Typography Helpers
|
||||
// 6. Abide
|
||||
// 7. Accordion
|
||||
// 8. Accordion Menu
|
||||
// 9. Badge
|
||||
// 10. Breadcrumbs
|
||||
// 11. Button
|
||||
// 12. Button Group
|
||||
// 13. Callout
|
||||
// 14. Card
|
||||
// 15. Close Button
|
||||
// 16. Drilldown
|
||||
// 17. Dropdown
|
||||
// 18. Dropdown Menu
|
||||
// 19. Forms
|
||||
// 20. Label
|
||||
// 21. Media Object
|
||||
// 22. Menu
|
||||
// 23. Meter
|
||||
// 24. Off-canvas
|
||||
// 25. Orbit
|
||||
// 26. Pagination
|
||||
// 27. Progress Bar
|
||||
// 28. Responsive Embed
|
||||
// 29. Reveal
|
||||
// 30. Slider
|
||||
// 31. Switch
|
||||
// 32. Table
|
||||
// 33. Tabs
|
||||
// 34. Thumbnail
|
||||
// 35. Title Bar
|
||||
// 36. Tooltip
|
||||
// 37. Top Bar
|
||||
|
||||
@import "~foundation-sites/scss/util/util";
|
||||
|
||||
// 1. Global
|
||||
// ---------
|
||||
|
||||
$global-font-size: 10px;
|
||||
$global-width: 100%;
|
||||
$global-lineheight: 1.5;
|
||||
$foundation-palette: (
|
||||
primary: $color-woot,
|
||||
secondary: #777,
|
||||
success: #13ce66,
|
||||
warning: #ffc82c,
|
||||
alert: #ff4949
|
||||
);
|
||||
$light-gray: #c0ccda;
|
||||
$medium-gray: #8492a6;
|
||||
$dark-gray: $color-gray;
|
||||
$black: #000000;
|
||||
$white: #fff;
|
||||
$body-background: $white;
|
||||
$body-font-color: $color-body;
|
||||
$body-font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
$body-antialiased: true;
|
||||
$global-margin: $space-one;
|
||||
$global-padding: $space-one;
|
||||
$global-weight-normal: normal;
|
||||
$global-weight-bold: bold;
|
||||
$global-radius: 0;
|
||||
$global-text-direction: ltr;
|
||||
$global-flexbox: false;
|
||||
$print-transparent-backgrounds: true;
|
||||
|
||||
@include add-foundation-colors;
|
||||
|
||||
// 2. Breakpoints
|
||||
// --------------
|
||||
|
||||
$breakpoints: (
|
||||
small: 0,
|
||||
medium: 640px,
|
||||
large: 1024px,
|
||||
xlarge: 1200px,
|
||||
xxlarge: 1440px
|
||||
);
|
||||
$print-breakpoint: large;
|
||||
$breakpoint-classes: (small medium large);
|
||||
|
||||
// 3. The Grid
|
||||
// -----------
|
||||
|
||||
$grid-row-width: $global-width;
|
||||
$grid-column-count: 12;
|
||||
$grid-column-gutter: (
|
||||
small: $zero,
|
||||
medium: $zero
|
||||
);
|
||||
$grid-column-align-edge: true;
|
||||
$block-grid-max: 8;
|
||||
|
||||
// 4. Base Typography
|
||||
// ------------------
|
||||
|
||||
$header-font-family: $body-font-family;
|
||||
$header-font-weight: $global-weight-normal;
|
||||
$header-font-style: normal;
|
||||
$font-family-monospace: $body-font-family;
|
||||
$header-color: $color-heading;
|
||||
$header-lineheight: 1.4;
|
||||
$header-margin-bottom: 0.5rem;
|
||||
$header-styles: (
|
||||
small: (
|
||||
"h1": (
|
||||
"font-size": 24
|
||||
),
|
||||
"h2": (
|
||||
"font-size": 20
|
||||
),
|
||||
"h3": (
|
||||
"font-size": 19
|
||||
),
|
||||
"h4": (
|
||||
"font-size": 18
|
||||
),
|
||||
"h5": (
|
||||
"font-size": 17
|
||||
),
|
||||
"h6": (
|
||||
"font-size": 16
|
||||
)
|
||||
),
|
||||
medium: (
|
||||
"h1": (
|
||||
"font-size": 48
|
||||
),
|
||||
"h2": (
|
||||
"font-size": 40
|
||||
),
|
||||
"h3": (
|
||||
"font-size": 31
|
||||
),
|
||||
"h4": (
|
||||
"font-size": 25
|
||||
),
|
||||
"h5": (
|
||||
"font-size": 20
|
||||
),
|
||||
"h6": (
|
||||
"font-size": 16
|
||||
)
|
||||
)
|
||||
);
|
||||
$header-text-rendering: optimizeLegibility;
|
||||
$small-font-size: 80%;
|
||||
$header-small-font-color: $medium-gray;
|
||||
$paragraph-lineheight: 1.6;
|
||||
$paragraph-margin-bottom: 1rem;
|
||||
$paragraph-text-rendering: optimizeLegibility;
|
||||
$code-color: $black;
|
||||
$code-font-family: $font-family-monospace;
|
||||
$code-font-weight: $global-weight-normal;
|
||||
$code-background: $light-gray;
|
||||
$code-border: 1px solid $medium-gray;
|
||||
$code-padding: rem-calc(2 5 1);
|
||||
$anchor-color: $primary-color;
|
||||
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
|
||||
$anchor-text-decoration: none;
|
||||
$anchor-text-decoration-hover: none;
|
||||
$hr-width: $global-width;
|
||||
$hr-border: 1px solid $medium-gray;
|
||||
$hr-margin: rem-calc(20) auto;
|
||||
$list-lineheight: $paragraph-lineheight;
|
||||
$list-margin-bottom: $paragraph-margin-bottom;
|
||||
$list-style-type: disc;
|
||||
$list-style-position: outside;
|
||||
$list-side-margin: 1.25rem;
|
||||
$list-nested-side-margin: 1.25rem;
|
||||
$defnlist-margin-bottom: 1rem;
|
||||
$defnlist-term-weight: $global-weight-bold;
|
||||
$defnlist-term-margin-bottom: 0.3rem;
|
||||
$blockquote-color: $dark-gray;
|
||||
$blockquote-padding: rem-calc(9 20 0 19);
|
||||
$blockquote-border: 1px solid $medium-gray;
|
||||
$cite-font-size: rem-calc(13);
|
||||
$cite-color: $dark-gray;
|
||||
$cite-pseudo-content: "\2014 \0020";
|
||||
$keystroke-font: $font-family-monospace;
|
||||
$keystroke-color: $black;
|
||||
$keystroke-background: $light-gray;
|
||||
$keystroke-padding: rem-calc(2 4 0);
|
||||
$keystroke-radius: $global-radius;
|
||||
$abbr-underline: 1px dotted $black;
|
||||
|
||||
// 5. Typography Helpers
|
||||
// ---------------------
|
||||
|
||||
$lead-font-size: $global-font-size * 1.25;
|
||||
$lead-lineheight: 1.6;
|
||||
$subheader-lineheight: 1.4;
|
||||
$subheader-color: $dark-gray;
|
||||
$subheader-font-weight: $global-weight-normal;
|
||||
$subheader-margin-top: 0.2rem;
|
||||
$subheader-margin-bottom: 0.5rem;
|
||||
$stat-font-size: 2.5rem;
|
||||
|
||||
// 6. Abide
|
||||
// --------
|
||||
|
||||
$abide-inputs: true;
|
||||
$abide-labels: true;
|
||||
$input-background-invalid: get-color(alert);
|
||||
$form-label-color-invalid: get-color(alert);
|
||||
$input-error-color: get-color(alert);
|
||||
$input-error-font-size: rem-calc(12);
|
||||
$input-error-font-weight: $global-weight-bold;
|
||||
|
||||
// 7. Accordion
|
||||
// ------------
|
||||
|
||||
$accordion-background: $white;
|
||||
$accordion-plusminus: true;
|
||||
$accordion-title-font-size: rem-calc(12);
|
||||
$accordion-item-color: $primary-color;
|
||||
$accordion-item-background-hover: $light-gray;
|
||||
$accordion-item-padding: 1.25rem 1rem;
|
||||
$accordion-content-background: $white;
|
||||
$accordion-content-border: 1px solid $light-gray;
|
||||
$accordion-content-color: $body-font-color;
|
||||
$accordion-content-padding: 1rem;
|
||||
|
||||
// 8. Accordion Menu
|
||||
// -----------------
|
||||
|
||||
$accordionmenu-arrows: true;
|
||||
$accordionmenu-arrow-color: $primary-color;
|
||||
$accordionmenu-arrow-size: 6px;
|
||||
|
||||
// 9. Badge
|
||||
// --------
|
||||
|
||||
$badge-background: $primary-color;
|
||||
$badge-color: $white;
|
||||
$badge-color-alt: $black;
|
||||
$badge-palette: $foundation-palette;
|
||||
$badge-padding: 0.3em;
|
||||
$badge-minwidth: 2.1em;
|
||||
$badge-font-size: 0.6rem;
|
||||
|
||||
// 10. Breadcrumbs
|
||||
// ---------------
|
||||
|
||||
$breadcrumbs-margin: 0 0 $global-margin 0;
|
||||
$breadcrumbs-item-font-size: rem-calc(11);
|
||||
$breadcrumbs-item-color: $primary-color;
|
||||
$breadcrumbs-item-color-current: $black;
|
||||
$breadcrumbs-item-color-disabled: $medium-gray;
|
||||
$breadcrumbs-item-margin: 0.75rem;
|
||||
$breadcrumbs-item-uppercase: true;
|
||||
$breadcrumbs-item-slash: true;
|
||||
|
||||
// 11. Button
|
||||
// ----------
|
||||
|
||||
$button-padding: $space-one $space-two;
|
||||
$button-margin: 0 0 $global-margin 0;
|
||||
$button-fill: solid;
|
||||
$button-background: $primary-color;
|
||||
$button-background-hover: scale-color($button-background, $lightness: -15%);
|
||||
$button-color: $black;
|
||||
$button-color-alt: $black;
|
||||
$button-radius: $global-radius;
|
||||
$button-sizes: (
|
||||
tiny: $font-size-micro,
|
||||
small: $font-size-mini,
|
||||
default: $font-size-default,
|
||||
large: $font-size-large
|
||||
);
|
||||
$button-palette: $foundation-palette;
|
||||
$button-opacity-disabled: 0.25;
|
||||
$button-background-hover-lightness: -20%;
|
||||
$button-hollow-hover-lightness: -50%;
|
||||
$button-transition: background-color 0.25s ease-out, color 0.25s ease-out;
|
||||
|
||||
// 12. Button Group
|
||||
// ----------------
|
||||
|
||||
$buttongroup-margin: 1rem;
|
||||
$buttongroup-spacing: 1px;
|
||||
$buttongroup-child-selector: ".button";
|
||||
$buttongroup-expand-max: 6;
|
||||
$buttongroup-radius-on-each: true;
|
||||
|
||||
// 13. Callout
|
||||
// -----------
|
||||
|
||||
$callout-background: $white;
|
||||
$callout-background-fade: 85%;
|
||||
$callout-border: 1px solid rgba($black, 0.25);
|
||||
$callout-margin: 0 0 1rem 0;
|
||||
$callout-padding: 1rem;
|
||||
$callout-font-color: $body-font-color;
|
||||
$callout-font-color-alt: $body-background;
|
||||
$callout-radius: $global-radius;
|
||||
$callout-link-tint: 30%;
|
||||
|
||||
// 14. Card
|
||||
// --------
|
||||
|
||||
$card-background: $white;
|
||||
$card-font-color: $body-font-color;
|
||||
$card-divider-background: $light-gray;
|
||||
$card-border: 1px solid $light-gray;
|
||||
$card-shadow: none;
|
||||
$card-border-radius: $global-radius;
|
||||
$card-padding: $global-padding;
|
||||
$card-margin: $global-margin;
|
||||
|
||||
// 15. Close Button
|
||||
// ----------------
|
||||
|
||||
$closebutton-position: right top;
|
||||
$closebutton-offset-horizontal: (
|
||||
small: 0.66rem,
|
||||
medium: 1rem
|
||||
);
|
||||
$closebutton-offset-vertical: (
|
||||
small: 0.33em,
|
||||
medium: 0.5rem
|
||||
);
|
||||
$closebutton-size: (
|
||||
small: 1.5em,
|
||||
medium: 2em
|
||||
);
|
||||
$closebutton-lineheight: 1;
|
||||
$closebutton-color: $dark-gray;
|
||||
$closebutton-color-hover: $black;
|
||||
|
||||
// 16. Drilldown
|
||||
// -------------
|
||||
|
||||
$drilldown-transition: transform 0.15s linear;
|
||||
$drilldown-arrows: true;
|
||||
$drilldown-arrow-color: $primary-color;
|
||||
$drilldown-arrow-size: 6px;
|
||||
$drilldown-background: $white;
|
||||
|
||||
// 17. Dropdown
|
||||
// ------------
|
||||
|
||||
$dropdown-padding: 1rem;
|
||||
$dropdown-background: $body-background;
|
||||
$dropdown-border: 1px solid $medium-gray;
|
||||
$dropdown-font-size: 1rem;
|
||||
$dropdown-width: 300px;
|
||||
$dropdown-radius: $global-radius;
|
||||
$dropdown-sizes: (
|
||||
tiny: 100px,
|
||||
small: 200px,
|
||||
large: 400px
|
||||
);
|
||||
|
||||
// 18. Dropdown Menu
|
||||
// -----------------
|
||||
|
||||
$dropdownmenu-arrows: true;
|
||||
$dropdownmenu-arrow-color: $anchor-color;
|
||||
$dropdownmenu-arrow-size: 6px;
|
||||
$dropdownmenu-min-width: 200px;
|
||||
$dropdownmenu-background: $white;
|
||||
$dropdownmenu-border: 1px solid $medium-gray;
|
||||
|
||||
// 19. Forms
|
||||
// ---------
|
||||
|
||||
$fieldset-border: 1px solid $light-gray;
|
||||
$fieldset-padding: $space-two;
|
||||
$fieldset-margin: $space-one $zero;
|
||||
$legend-padding: rem-calc(0 3);
|
||||
$form-spacing: $space-normal;
|
||||
$helptext-color: $header-color;
|
||||
$helptext-font-size: $font-size-small;
|
||||
$helptext-font-style: italic;
|
||||
$input-prefix-color: $header-color;
|
||||
$input-prefix-background: $light-gray;
|
||||
$input-prefix-border: 1px solid $light-gray;
|
||||
$input-prefix-padding: 1rem;
|
||||
$form-label-color: $header-color;
|
||||
$form-label-font-size: rem-calc(14);
|
||||
$form-label-font-weight: $font-weight-medium;
|
||||
$form-label-line-height: 1.8;
|
||||
$select-background: $white;
|
||||
$select-triangle-color: $dark-gray;
|
||||
$select-radius: $global-radius;
|
||||
$input-color: $header-color;
|
||||
$input-placeholder-color: $light-gray;
|
||||
$input-font-family: inherit;
|
||||
$input-font-size: $font-size-default;
|
||||
$input-font-weight: $global-weight-normal;
|
||||
$input-background: $white;
|
||||
$input-background-focus: $white;
|
||||
$input-background-disabled: $light-gray;
|
||||
$input-border: 1px solid $light-gray;
|
||||
$input-border-focus: 1px solid lighten($primary-color, 15%);
|
||||
$input-shadow: 0;
|
||||
$input-shadow-focus: 0;
|
||||
$input-cursor-disabled: not-allowed;
|
||||
$input-transition: border-color 0.25s ease-in-out;
|
||||
$input-number-spinners: true;
|
||||
$input-radius: $global-radius;
|
||||
$form-button-radius: $global-radius;
|
||||
|
||||
// 20. Label
|
||||
// ---------
|
||||
|
||||
$label-background: $primary-color;
|
||||
$label-color: $white;
|
||||
$label-color-alt: $black;
|
||||
$label-palette: $foundation-palette;
|
||||
$label-font-size: $font-size-micro;
|
||||
$label-padding: $space-micro $space-smaller;
|
||||
$label-radius: $space-micro;
|
||||
|
||||
// 21. Media Object
|
||||
// ----------------
|
||||
|
||||
$mediaobject-margin-bottom: $global-margin;
|
||||
$mediaobject-section-padding: $global-padding;
|
||||
$mediaobject-image-width-stacked: 100%;
|
||||
|
||||
// 22. Menu
|
||||
// --------
|
||||
|
||||
$menu-margin: 0;
|
||||
$menu-margin-nested: $space-medium;
|
||||
$menu-item-padding: $space-one;
|
||||
$menu-item-color-active: $white;
|
||||
$menu-item-background-active: $color-background;
|
||||
$menu-icon-spacing: 0.25rem;
|
||||
$menu-item-background-hover: $light-gray;
|
||||
$menu-border: $light-gray;
|
||||
|
||||
// 23. Meter
|
||||
// ---------
|
||||
|
||||
$meter-height: 1rem;
|
||||
$meter-radius: $global-radius;
|
||||
$meter-background: $medium-gray;
|
||||
$meter-fill-good: $success-color;
|
||||
$meter-fill-medium: $warning-color;
|
||||
$meter-fill-bad: $alert-color;
|
||||
|
||||
// 24. Off-canvas
|
||||
// --------------
|
||||
|
||||
$offcanvas-size: 250px;
|
||||
$offcanvas-vertical-size: 250px;
|
||||
$offcanvas-background: $light-gray;
|
||||
$offcanvas-shadow: 0 0 10px rgba($black, 0.7);
|
||||
$offcanvas-push-zindex: 1;
|
||||
$offcanvas-overlap-zindex: 10;
|
||||
$offcanvas-reveal-zindex: 1;
|
||||
$offcanvas-transition-length: 0.5s;
|
||||
$offcanvas-transition-timing: ease;
|
||||
$offcanvas-fixed-reveal: true;
|
||||
$offcanvas-exit-background: rgba($white, 0.25);
|
||||
$maincontent-class: "off-canvas-content";
|
||||
|
||||
// 25. Orbit
|
||||
// ---------
|
||||
|
||||
$orbit-bullet-background: $medium-gray;
|
||||
$orbit-bullet-background-active: $dark-gray;
|
||||
$orbit-bullet-diameter: 1.2rem;
|
||||
$orbit-bullet-margin: 0.1rem;
|
||||
$orbit-bullet-margin-top: 0.8rem;
|
||||
$orbit-bullet-margin-bottom: 0.8rem;
|
||||
$orbit-caption-background: rgba($black, 0.5);
|
||||
$orbit-caption-padding: 1rem;
|
||||
$orbit-control-background-hover: rgba($black, 0.5);
|
||||
$orbit-control-padding: 1rem;
|
||||
$orbit-control-zindex: 10;
|
||||
|
||||
// 26. Pagination
|
||||
// --------------
|
||||
|
||||
$pagination-font-size: rem-calc(14);
|
||||
$pagination-margin-bottom: $global-margin;
|
||||
$pagination-item-color: $black;
|
||||
$pagination-item-padding: rem-calc(3 10);
|
||||
$pagination-item-spacing: rem-calc(1);
|
||||
$pagination-radius: $global-radius;
|
||||
$pagination-item-background-hover: $light-gray;
|
||||
$pagination-item-background-current: $primary-color;
|
||||
$pagination-item-color-current: $white;
|
||||
$pagination-item-color-disabled: $medium-gray;
|
||||
$pagination-ellipsis-color: $black;
|
||||
$pagination-mobile-items: false;
|
||||
$pagination-mobile-current-item: false;
|
||||
$pagination-arrows: true;
|
||||
|
||||
// 27. Progress Bar
|
||||
// ----------------
|
||||
|
||||
$progress-height: 1rem;
|
||||
$progress-background: $medium-gray;
|
||||
$progress-margin-bottom: $global-margin;
|
||||
$progress-meter-background: $primary-color;
|
||||
$progress-radius: $global-radius;
|
||||
|
||||
// 28. Responsive Embed
|
||||
// --------------------
|
||||
|
||||
$responsive-embed-margin-bottom: rem-calc(16);
|
||||
$responsive-embed-ratios: (
|
||||
default: 4 by 3,
|
||||
widescreen: 16 by 9
|
||||
);
|
||||
|
||||
// 29. Reveal
|
||||
// ----------
|
||||
|
||||
$reveal-background: $white;
|
||||
$reveal-width: 600px;
|
||||
$reveal-max-width: $global-width;
|
||||
$reveal-padding: $global-padding;
|
||||
$reveal-border: 1px solid $medium-gray;
|
||||
$reveal-radius: $global-radius;
|
||||
$reveal-zindex: 1005;
|
||||
$reveal-overlay-background: rgba($black, 0.45);
|
||||
|
||||
// 30. Slider
|
||||
// ----------
|
||||
|
||||
$slider-width-vertical: 0.5rem;
|
||||
$slider-transition: all 0.2s ease-in-out;
|
||||
$slider-height: 0.5rem;
|
||||
$slider-background: $light-gray;
|
||||
$slider-fill-background: $medium-gray;
|
||||
$slider-handle-height: 1.4rem;
|
||||
$slider-handle-width: 1.4rem;
|
||||
$slider-handle-background: $primary-color;
|
||||
$slider-opacity-disabled: 0.25;
|
||||
$slider-radius: $global-radius;
|
||||
|
||||
// 31. Switch
|
||||
// ----------
|
||||
|
||||
$switch-background: $light-gray;
|
||||
$switch-background-active: $primary-color;
|
||||
$switch-height: $space-two;
|
||||
$switch-height-tiny: $space-slab;
|
||||
$switch-height-small: $space-normal;
|
||||
$switch-height-large: $space-large;
|
||||
$switch-radius: $space-large;
|
||||
$switch-margin: $global-margin;
|
||||
$switch-paddle-background: $white;
|
||||
$switch-paddle-offset: $space-micro;
|
||||
$switch-paddle-radius: $space-large;
|
||||
$switch-paddle-transition: all 0.15s ease-out;
|
||||
|
||||
// 32. Table
|
||||
// ---------
|
||||
|
||||
$table-background: transparent;
|
||||
$table-color-scale: 5%;
|
||||
$table-border: 1px solid smart-scale($color-heading, $table-color-scale);
|
||||
$table-padding: rem-calc(8 10 10);
|
||||
$table-hover-scale: 2%;
|
||||
$table-row-hover: darken($table-background, $table-hover-scale);
|
||||
$table-row-stripe-hover: darken(
|
||||
$table-background,
|
||||
$table-color-scale + $table-hover-scale
|
||||
);
|
||||
$table-is-striped: false;
|
||||
$table-striped-background: smart-scale($table-background, $table-color-scale);
|
||||
$table-stripe: even;
|
||||
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
|
||||
$table-head-row-hover: darken($table-head-background, $table-hover-scale);
|
||||
$table-foot-background: smart-scale($table-background, $table-color-scale);
|
||||
$table-foot-row-hover: darken($table-foot-background, $table-hover-scale);
|
||||
$table-head-font-color: $body-font-color;
|
||||
$table-foot-font-color: $body-font-color;
|
||||
$show-header-for-stacked: false;
|
||||
|
||||
// 33. Tabs
|
||||
// --------
|
||||
|
||||
$tab-margin: 0;
|
||||
|
||||
$tab-background: transparent;
|
||||
$tab-background-active: transparent;
|
||||
$tab-item-font-size: $font-size-small;
|
||||
$tab-item-background-hover: transparent;
|
||||
$tab-item-padding: $space-one $zero;
|
||||
$tab-color: $primary-color;
|
||||
$tab-active-color: $primary-color;
|
||||
$tab-expand-max: 6;
|
||||
$tab-content-background: transparent;
|
||||
$tab-content-border: transparent;
|
||||
$tab-content-color: foreground($tab-background, $primary-color);
|
||||
$tab-content-padding: 1rem;
|
||||
|
||||
// 34. Thumbnail
|
||||
// -------------
|
||||
|
||||
$thumbnail-border: solid 4px $white;
|
||||
$thumbnail-margin-bottom: $global-margin;
|
||||
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
|
||||
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
|
||||
$thumbnail-transition: box-shadow 200ms ease-out;
|
||||
$thumbnail-radius: $global-radius;
|
||||
|
||||
// 35. Title Bar
|
||||
// -------------
|
||||
|
||||
$titlebar-background: $black;
|
||||
$titlebar-color: $white;
|
||||
$titlebar-padding: 0.5rem;
|
||||
$titlebar-text-font-weight: bold;
|
||||
$titlebar-icon-color: $white;
|
||||
$titlebar-icon-color-hover: $medium-gray;
|
||||
$titlebar-icon-spacing: 0.25rem;
|
||||
|
||||
// 36. Tooltip
|
||||
// -----------
|
||||
|
||||
$has-tip-font-weight: $global-weight-bold;
|
||||
$has-tip-border-bottom: dotted 1px $dark-gray;
|
||||
$tooltip-background-color: $black;
|
||||
$tooltip-color: $white;
|
||||
$tooltip-padding: 0.75rem;
|
||||
$tooltip-font-size: $font-size-mini;
|
||||
$tooltip-pip-width: 0.75rem;
|
||||
$tooltip-pip-height: $tooltip-pip-width * 0.866;
|
||||
$tooltip-radius: $global-radius;
|
||||
|
||||
// 37. Top Bar
|
||||
// -----------
|
||||
|
||||
$topbar-padding: 0.5rem;
|
||||
$topbar-background: $light-gray;
|
||||
$topbar-submenu-background: $topbar-background;
|
||||
$topbar-title-spacing: 0.5rem 1rem 0.5rem 0;
|
||||
$topbar-input-width: 200px;
|
||||
$topbar-unstack-breakpoint: medium;
|
||||
58
app/javascript/dashboard/assets/scss/_helper-classes.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
.bg-light {
|
||||
@include background-light;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
@include flex;
|
||||
@include flex-align(center, middle);
|
||||
}
|
||||
|
||||
.bottom-space-fix {
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
@include full-height();
|
||||
}
|
||||
|
||||
.spinner {
|
||||
@include color-spinner();
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: $space-medium;
|
||||
height: $space-medium;
|
||||
padding: $zero $space-medium;
|
||||
vertical-align: middle;
|
||||
|
||||
&.message {
|
||||
padding: $space-normal;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0 auto;
|
||||
margin-top: $space-slab;
|
||||
background: $color-white;
|
||||
border-radius: $space-large;
|
||||
@include elegent-shadow;
|
||||
|
||||
&:before {
|
||||
margin-top: -$space-slab;
|
||||
margin-left: -$space-slab;
|
||||
}
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: $space-normal;
|
||||
height: $space-normal;
|
||||
|
||||
&:before {
|
||||
width: $space-normal;
|
||||
height: $space-normal;
|
||||
margin-top: -$space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
input, textarea {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
79
app/javascript/dashboard/assets/scss/_layout.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
@include full-height;
|
||||
@include flex-weight(1);
|
||||
width: 100%;
|
||||
}
|
||||
.app-root {
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
}
|
||||
.app-content {
|
||||
@include flex;
|
||||
}
|
||||
|
||||
.view-box {
|
||||
@include full-height;
|
||||
height: 100vh;
|
||||
@include margin(0);
|
||||
@include space-between-column;
|
||||
}
|
||||
|
||||
.view-panel {
|
||||
@include flex-weight(1);
|
||||
@include flex-direction(column);
|
||||
@include margin($zero);
|
||||
@include padding($space-normal);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
overflow: scroll;
|
||||
@include padding($space-normal);
|
||||
.btn-fixed-right-top {
|
||||
position: fixed;
|
||||
top: $space-small;
|
||||
right: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
.back-button {
|
||||
color: $color-woot;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-normal;
|
||||
margin-right: $space-normal;
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
vertical-align: text-bottom;
|
||||
margin-right: $space-smaller;
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
.button-spinner {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.no-items-error-message {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include justify-content(center);
|
||||
@include align-items(center);
|
||||
flex-direction: column;
|
||||
|
||||
img {
|
||||
max-width: $space-mega;
|
||||
@include padding($space-one);
|
||||
}
|
||||
}
|
||||
206
app/javascript/dashboard/assets/scss/_mixins.scss
Normal file
@@ -0,0 +1,206 @@
|
||||
//borders
|
||||
@mixin border-nil() {
|
||||
border-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@mixin thin-border($color) {
|
||||
border: 1px solid $color;
|
||||
}
|
||||
|
||||
@mixin custom-border-bottom($size, $color) {
|
||||
border-bottom: $size solid $color;
|
||||
}
|
||||
|
||||
@mixin custom-border-top($size, $color) {
|
||||
border-top: $size solid $color;
|
||||
}
|
||||
|
||||
@mixin border-normal() {
|
||||
border: 1px solid $color-border;
|
||||
}
|
||||
@mixin border-normal-left() {
|
||||
border-left: 1px solid $color-border;
|
||||
}
|
||||
@mixin border-normal-top() {
|
||||
border-top: 1px solid $color-border;
|
||||
}
|
||||
@mixin border-normal-right() {
|
||||
border-right: 1px solid $color-border;
|
||||
}
|
||||
@mixin border-normal-bottom() {
|
||||
border-bottom: 1px solid $color-border;
|
||||
}
|
||||
|
||||
@mixin border-light() {
|
||||
border: 1px solid $color-border-light;
|
||||
}
|
||||
@mixin border-light-left() {
|
||||
border-left: 1px solid $color-border-light;
|
||||
}
|
||||
@mixin border-light-top() {
|
||||
border-top: 1px solid $color-border-light;
|
||||
}
|
||||
@mixin border-light-right() {
|
||||
border-right: 1px solid $color-border-light;
|
||||
}
|
||||
@mixin border-light-bottom() {
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
}
|
||||
|
||||
// background
|
||||
@mixin background-gray() {
|
||||
background: $color-background;
|
||||
}
|
||||
|
||||
@mixin background-light() {
|
||||
background: $color-background-light;
|
||||
}
|
||||
|
||||
@mixin background-white() {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
// input form
|
||||
@mixin ghost-input() {
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// flex-layout
|
||||
@mixin space-between() {
|
||||
@include display(flex);
|
||||
@include justify-content(space-between);
|
||||
}
|
||||
|
||||
@mixin space-between-column() {
|
||||
@include space-between;
|
||||
@include flex-direction(column);
|
||||
}
|
||||
@mixin space-between-row() {
|
||||
@include space-between;
|
||||
@include flex-direction(row);
|
||||
}
|
||||
|
||||
@mixin flex-shrink() {
|
||||
flex: flex-grid-column(shrink);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@mixin flex-weight($value) {
|
||||
// Grab flex-grow for older browsers.
|
||||
$flex-grow: nth($value, 1);
|
||||
|
||||
// 2009
|
||||
@include prefixer(box-flex, $flex-grow, webkit moz spec);
|
||||
|
||||
// 2011 (IE 10), 2012
|
||||
@include prefixer(flex, $value, webkit moz ms spec);
|
||||
}
|
||||
|
||||
// full height
|
||||
@mixin full-height() {
|
||||
height: 100%;
|
||||
// COmmenting because unneccessary scroll is apprearing on some pages eg: settings/agents / inboxes
|
||||
}
|
||||
@mixin round-corner() {
|
||||
border-radius: 1000px;
|
||||
}
|
||||
|
||||
@mixin scroll-on-hover() {
|
||||
@include transition(all .4s $ease-in-out-cubic);
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mixin horizontal-scroll() {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@mixin elegent-shadow() {
|
||||
box-shadow: 0 10px 25px 0 rgba(49,49,93,0.15);
|
||||
}
|
||||
|
||||
@mixin elegant-card() {
|
||||
@include elegent-shadow;
|
||||
border-radius: $space-small;
|
||||
}
|
||||
|
||||
@mixin color-spinner() {
|
||||
@keyframes spinner {
|
||||
to {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: $space-medium;
|
||||
height: $space-medium;
|
||||
margin-top: -$space-one;
|
||||
margin-left: -$space-one;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.7);
|
||||
border-top-color: lighten($color-woot, 10%);
|
||||
animation: spinner .9s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// arrows
|
||||
// --------------------------------------------------------
|
||||
// $direction: top, left, right, bottom, top-left, top-right, bottom-left, bottom-right
|
||||
// $color: hex, rgb or rbga
|
||||
// $size: px or em
|
||||
// @example
|
||||
// .element{
|
||||
// @include arrow(top, #000, 50px);
|
||||
// }
|
||||
@mixin arrow($direction, $color, $size){
|
||||
display: block;
|
||||
height: 0;
|
||||
width: 0;
|
||||
content: '';
|
||||
|
||||
@if $direction == 'top' {
|
||||
border-left: $size solid transparent;
|
||||
border-right: $size solid transparent;
|
||||
border-bottom: $size solid $color;
|
||||
} @else if $direction == 'right' {
|
||||
border-top: $size solid transparent;
|
||||
border-bottom: $size solid transparent;
|
||||
border-left: $size solid $color;
|
||||
} @else if $direction == 'bottom' {
|
||||
border-top: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
border-left: $size solid transparent;
|
||||
} @else if $direction == 'left' {
|
||||
border-top: $size solid transparent;
|
||||
border-right: $size solid $color;
|
||||
border-bottom: $size solid transparent;
|
||||
} @else if $direction == 'top-left' {
|
||||
border-top: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'top-right' {
|
||||
border-top: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
} @else if $direction == 'bottom-left' {
|
||||
border-bottom: $size solid $color;
|
||||
border-right: $size solid transparent;
|
||||
} @else if $direction == 'bottom-right' {
|
||||
border-bottom: $size solid $color;
|
||||
border-left: $size solid transparent;
|
||||
}
|
||||
}
|
||||
27
app/javascript/dashboard/assets/scss/_typography.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
.page-title {
|
||||
font-size: $font-size-big;
|
||||
}
|
||||
|
||||
.page-sub-title {
|
||||
font-size: $font-size-large;
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-size: $font-size-medium;
|
||||
}
|
||||
|
||||
.sub-block-title {
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
.text-block-title {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
84
app/javascript/dashboard/assets/scss/_variables.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
// Font sizes
|
||||
$font-size-nano: 0.8rem;
|
||||
$font-size-micro: 1.0rem;
|
||||
$font-size-mini: 1.2rem;
|
||||
$font-size-small: 1.4rem;
|
||||
$font-size-default: 1.6rem;
|
||||
$font-size-medium: 1.8rem;
|
||||
$font-size-large: 2.2rem;
|
||||
$font-size-big: 2.4rem;
|
||||
$font-size-bigger: 3.0rem;
|
||||
$font-size-mega: 3.4rem;
|
||||
$font-size-giga: 4.0rem;
|
||||
|
||||
// spaces
|
||||
$zero: 0rem;
|
||||
$space-micro: 0.2rem;
|
||||
$space-smaller: 0.4rem;
|
||||
$space-small: 0.8rem;
|
||||
$space-one: 1rem;
|
||||
$space-slab: 1.2rem;
|
||||
$space-normal: 1.6rem;
|
||||
$space-two: 2.0rem;
|
||||
$space-medium: 2.4rem;
|
||||
$space-large: 3.2rem;
|
||||
$space-larger: 4.8rem;
|
||||
$space-jumbo: 6.4rem;
|
||||
$space-mega: 10.0rem;
|
||||
|
||||
// font-weight
|
||||
$font-weight-feather: 100;
|
||||
$font-weight-light: 300;
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-bold: 600;
|
||||
$font-weight-black: 700;
|
||||
|
||||
//Navbar
|
||||
$nav-bar-width: 23rem;
|
||||
$header-height: 5.6rem;
|
||||
|
||||
// Woot Logo
|
||||
$woot-logo-width: 20rem;
|
||||
$woot-logo-height: 8rem;
|
||||
$woot-logo-padding: $space-large $space-large $space-large $space-large;
|
||||
|
||||
// Colors
|
||||
$color-woot: #1f93ff;
|
||||
$color-gray: #6E6F73;
|
||||
$color-light-gray: #999A9B;
|
||||
$color-border: #E0E6ED;
|
||||
$color-border-light: #f0f4f5;
|
||||
$color-background: #EFF2F7;
|
||||
$color-background-light: #F9FAFC;
|
||||
$color-white: #FFF;
|
||||
$color-body: #3C4858;
|
||||
$color-heading: #1F2D3D;
|
||||
$color-modal-header: #f1f1f1;
|
||||
$color-extra-light-blue: #F5F7F9;
|
||||
|
||||
// Thumbnail
|
||||
$thumbnail-radius: 4rem;
|
||||
|
||||
// chat-header
|
||||
$conv-header-height: 4rem;
|
||||
|
||||
// login
|
||||
|
||||
// Inbox List
|
||||
|
||||
$inbox-thumb-size: 4.8rem;
|
||||
|
||||
|
||||
// Spinner
|
||||
$spinkit-spinner-color: $color-white !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
|
||||
$spinkit-size: 1.6rem !default;
|
||||
|
||||
// Snackbar default
|
||||
$woot-snackbar-bg: #323232;
|
||||
$woot-snackbar-button: #ffeb3b;
|
||||
|
||||
$swift-ease-out-duration: .4s !default;
|
||||
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
|
||||
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
|
||||
30
app/javascript/dashboard/assets/scss/_woot.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
@import 'typography';
|
||||
@import 'layout';
|
||||
@import 'animations';
|
||||
|
||||
@import 'foundation-custom';
|
||||
@import 'widgets/search-box';
|
||||
@import 'widgets/conv-header';
|
||||
@import 'widgets/thumbnail';
|
||||
@import 'widgets/conversation-card';
|
||||
@import 'widgets/conversation-view';
|
||||
@import 'widgets/reply-box';
|
||||
@import 'widgets/tabs';
|
||||
@import 'widgets/login';
|
||||
@import 'widgets/emojiinput';
|
||||
@import 'widgets/woot-tables';
|
||||
@import 'widgets/sidemenu';
|
||||
@import 'widgets/forms';
|
||||
@import 'widgets/buttons';
|
||||
@import 'widgets/snackbar';
|
||||
@import 'widgets/modal';
|
||||
@import 'widgets/states';
|
||||
@import 'widgets/report';
|
||||
@import 'widgets/billing';
|
||||
@import 'widgets/status-bar';
|
||||
|
||||
@import 'views/settings/inbox';
|
||||
@import 'views/settings/channel';
|
||||
@import 'views/signup';
|
||||
|
||||
@import 'plugins/multiselect';
|
||||
9
app/javascript/dashboard/assets/scss/app.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@import '~bourbon/app/assets/stylesheets/bourbon';
|
||||
@import 'variables';
|
||||
@import '~spinkit/scss/spinners/7-three-bounce';
|
||||
@import url('https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.css');
|
||||
@import 'foundation-settings';
|
||||
@import 'mixins';
|
||||
@import 'helper-classes';
|
||||
@import '~foundation-sites/assets/foundation-flex';
|
||||
@import 'woot';
|
||||
@@ -0,0 +1,26 @@
|
||||
.multiselect {
|
||||
min-height: 38px;
|
||||
margin-bottom: $space-normal;
|
||||
|
||||
> .multiselect__tags {
|
||||
padding-top: $zero;
|
||||
min-height: 38px;
|
||||
border-radius: 0;
|
||||
border: 1px solid $light-gray;
|
||||
@include margin(0);
|
||||
|
||||
.multiselect__tag {
|
||||
margin-top: $space-smaller;
|
||||
}
|
||||
|
||||
.multiselect__input {
|
||||
@include ghost-input;
|
||||
margin-bottom: $zero;
|
||||
@include padding($zero);
|
||||
}
|
||||
.multiselect__single {
|
||||
@include padding($space-small);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
app/javascript/dashboard/assets/scss/views/_signup.scss
Normal file
@@ -0,0 +1,91 @@
|
||||
.signup {
|
||||
// margin-top: $space-larger*1.2;
|
||||
|
||||
.signup--hero {
|
||||
margin-bottom: $space-larger*1.5;
|
||||
|
||||
.hero--logo {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.hero--title {
|
||||
margin-top: $space-large;
|
||||
font-weight: $font-weight-light;
|
||||
}
|
||||
|
||||
.hero--sub {
|
||||
font-size: $font-size-medium;
|
||||
color: $medium-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.signup--features {
|
||||
list-style-type: none;
|
||||
font-size: $font-size-medium;
|
||||
|
||||
> li {
|
||||
padding: $space-slab;
|
||||
|
||||
> i {
|
||||
margin-right: $space-two;
|
||||
font-size: $font-size-large;
|
||||
|
||||
&.beer {
|
||||
color: #dfb63b;
|
||||
}
|
||||
|
||||
&.report {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
&.canned {
|
||||
color: #1cad22;
|
||||
}
|
||||
|
||||
&.uptime {
|
||||
color: #a753b5;
|
||||
}
|
||||
|
||||
&.secure {
|
||||
color: #607d8b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.signup--box {
|
||||
@include elegant-card;
|
||||
padding: $space-large;
|
||||
label {
|
||||
font-size: $font-size-default;
|
||||
color: $color-gray;
|
||||
|
||||
input {
|
||||
padding: $space-slab;
|
||||
height: $space-larger;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
.error {
|
||||
font-size: $font-size-small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sigin--footer {
|
||||
padding: $space-medium;
|
||||
font-size: $font-size-default;
|
||||
|
||||
> a {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.accept--terms {
|
||||
font-size: $font-size-mini;
|
||||
text-align: center;
|
||||
@include margin($zero);
|
||||
a {
|
||||
font-size: $font-size-mini;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
.channels {
|
||||
margin-top: $space-medium;
|
||||
.inactive {
|
||||
@include filter(grayscale(100%));
|
||||
}
|
||||
.channel {
|
||||
@include flex;
|
||||
@include padding($space-normal $zero);
|
||||
@include margin($zero);
|
||||
@include background-white;
|
||||
@include border-light;
|
||||
@include flex-direction(column);
|
||||
cursor: pointer;
|
||||
border-right-color: $color-white;
|
||||
@include transition(all 0.200s ease-in);
|
||||
|
||||
&:last-child {
|
||||
@include border-light;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $primary-color;
|
||||
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 50%;
|
||||
@include margin($space-normal auto);
|
||||
}
|
||||
|
||||
.channel__title{
|
||||
font-size: $font-size-large;
|
||||
text-align: center;
|
||||
color: $color-body;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
width: 100%;
|
||||
color: $medium-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
242
app/javascript/dashboard/assets/scss/views/settings/inbox.scss
Normal file
@@ -0,0 +1,242 @@
|
||||
// Conversation header - Light BG
|
||||
.settings-header {
|
||||
@include padding($space-small $space-normal);
|
||||
@include background-white;
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
@include border-normal-bottom;
|
||||
height: $header-height;
|
||||
min-height: $header-height;
|
||||
// Resolve Button
|
||||
.button {
|
||||
@include margin(0);
|
||||
}
|
||||
|
||||
// User thumbnail and text
|
||||
.page-title {
|
||||
@include flex;
|
||||
@include flex-align($x: center, $y: middle);
|
||||
@include margin($zero);
|
||||
> span {
|
||||
@include padding($zero $space-small $zero $space-small);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wizard-box {
|
||||
.item {
|
||||
@include padding($space-normal $space-normal $space-normal $space-medium);
|
||||
position: relative;
|
||||
@include background-light;
|
||||
cursor: pointer;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: $color-border;
|
||||
top: $space-normal;
|
||||
}
|
||||
|
||||
&:before {
|
||||
top: $zero;
|
||||
height: $space-normal;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
&:before {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&:after {
|
||||
height: $zero;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
// left: 1px;
|
||||
// @include background-white;
|
||||
// @include border-light;
|
||||
// border-right: 0;
|
||||
h3 {
|
||||
color: $color-woot;
|
||||
}
|
||||
|
||||
.step {
|
||||
background: $color-woot;
|
||||
}
|
||||
}
|
||||
|
||||
&.over {
|
||||
|
||||
&:after {
|
||||
background: $color-woot;
|
||||
}
|
||||
|
||||
.step {
|
||||
background: $color-woot;
|
||||
}
|
||||
|
||||
&+.item {
|
||||
&:before {
|
||||
background: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: $font-size-default;
|
||||
padding-left: $space-medium;
|
||||
line-height: 1;
|
||||
color: $color-body;
|
||||
|
||||
.completed {
|
||||
color: $success-color;
|
||||
}
|
||||
}
|
||||
p {
|
||||
font-size: $font-size-small;
|
||||
color: $color-light-gray;
|
||||
padding-left: $space-medium;
|
||||
margin: 0;
|
||||
}
|
||||
.step {
|
||||
position: absolute;
|
||||
left: $space-normal;
|
||||
top: $space-normal;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
background: $color-border;
|
||||
border-radius: 20px;
|
||||
width: $space-normal;
|
||||
height: $space-normal;
|
||||
text-align: center;
|
||||
line-height: $space-normal;
|
||||
color: #fff;
|
||||
z-index: 999;
|
||||
|
||||
i {
|
||||
font-size: $font-size-micro;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-body {
|
||||
@include background-white;
|
||||
@include padding($space-medium);
|
||||
@include border-light;
|
||||
@include full-height();
|
||||
}
|
||||
.inoboxes-list {
|
||||
// @include margin(auto);
|
||||
// @include background-white;
|
||||
// @include border-light;
|
||||
// width: 50%;
|
||||
|
||||
.inbox-item {
|
||||
@include margin($space-normal);
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
@include padding($space-normal $space-normal);
|
||||
@include border-light-bottom();
|
||||
flex-direction: column;
|
||||
background: $color-white;
|
||||
cursor: pointer;
|
||||
width: 20%;
|
||||
float: left;
|
||||
min-height: 10rem;
|
||||
&:last-child {
|
||||
margin-bottom: $zero;
|
||||
@include border-nil;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include background-gray;
|
||||
.arrow {
|
||||
opacity: 1;
|
||||
@include transform(translateX($space-small));
|
||||
}
|
||||
}
|
||||
.switch {
|
||||
align-self: center;
|
||||
margin-right: $space-normal;
|
||||
margin-bottom: $zero;
|
||||
}
|
||||
|
||||
.item--details {
|
||||
@include padding($space-normal $zero);
|
||||
.item--name {
|
||||
font-size: $font-size-large;
|
||||
line-height: 1;
|
||||
}
|
||||
.item--sub {
|
||||
margin-bottom: 0;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
}
|
||||
.arrow {
|
||||
align-self: center;
|
||||
font-size: $font-size-small;
|
||||
color: $medium-gray;
|
||||
opacity: .7;
|
||||
@include transform(translateX(0));
|
||||
@include transition(opacity 0.100s ease-in 0s, transform 0.200s ease-in 0.030s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-modal {
|
||||
width: 60%;
|
||||
height: 80%;
|
||||
.delete-wrapper {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
@include justify-content(space-between);
|
||||
@include padding($space-normal $space-large);
|
||||
a {
|
||||
margin-left: $space-normal;
|
||||
}
|
||||
}
|
||||
.code-wrapper {
|
||||
@include margin($space-medium);
|
||||
|
||||
.title {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
.code {
|
||||
max-height: $space-mega;
|
||||
overflow: scroll;
|
||||
white-space: nowrap;
|
||||
@include padding($space-one);
|
||||
background: $color-background;
|
||||
code {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.agent-wrapper {
|
||||
@include margin($space-medium);
|
||||
.title {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
.login-init {
|
||||
text-align: center;
|
||||
padding-top: 30%;
|
||||
p {
|
||||
@include padding($space-medium);
|
||||
}
|
||||
> a > img {
|
||||
width: $space-larger*5;
|
||||
}
|
||||
}
|
||||
76
app/javascript/dashboard/assets/scss/widgets/_billing.scss
Normal file
@@ -0,0 +1,76 @@
|
||||
.billing {
|
||||
@include full-height;
|
||||
|
||||
.row {
|
||||
@include full-height;
|
||||
}
|
||||
|
||||
.billing__stats {
|
||||
@include flex;
|
||||
}
|
||||
|
||||
.billing__form {
|
||||
@include thin-border($color-border-light);
|
||||
@include margin($zero - $space-micro);
|
||||
@include full-height;
|
||||
background: $color-white;
|
||||
|
||||
iframe {
|
||||
@include full-height;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.account-row {
|
||||
@include flex-grid-column(3, $space-medium);
|
||||
@include padding($space-normal);
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
// @include thin-border($color-border-light);
|
||||
// @include margin(-$space-micro $zero);
|
||||
background: $color-white;
|
||||
font-size: $font-size-small;
|
||||
|
||||
.title {
|
||||
color: $color-heading;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: $font-size-mega;
|
||||
font-weight: $font-weight-light;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-locked {
|
||||
@include background-gray;
|
||||
@include margin(0);
|
||||
}
|
||||
|
||||
.lock-message {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
|
||||
div {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
|
||||
img {
|
||||
@include margin($space-normal);
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/javascript/dashboard/assets/scss/widgets/_buttons.scss
Normal file
@@ -0,0 +1,30 @@
|
||||
.button {
|
||||
&.icon {
|
||||
padding-left: $space-normal;
|
||||
padding-right: $space-normal;
|
||||
i {
|
||||
padding-right: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
&.nice {
|
||||
border-radius: $space-smaller;
|
||||
}
|
||||
|
||||
&.hollow {
|
||||
&.link {
|
||||
border-color: transparent;
|
||||
padding-left: 0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: $font-size-mini;
|
||||
margin-right: $space-smaller;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Conversation header - Light BG
|
||||
.conv-header {
|
||||
@include padding($space-small $space-normal);
|
||||
@include background-white;
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
@include border-normal-bottom;
|
||||
// Resolve Button
|
||||
.button {
|
||||
@include margin(0);
|
||||
@include flex;
|
||||
}
|
||||
|
||||
.multiselect-box {
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
@include margin(0 $space-small);
|
||||
@include border-light;
|
||||
border-radius: $space-smaller;
|
||||
margin-right: $space-normal;
|
||||
|
||||
&:before {
|
||||
line-height: 3.8rem;
|
||||
font-size: $font-size-default;
|
||||
padding-left: $space-slab;
|
||||
padding-right: $space-smaller;
|
||||
color: $medium-gray;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
margin: 0;
|
||||
|
||||
.multiselect__tags {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User thumbnail and text
|
||||
.user {
|
||||
@include flex;
|
||||
@include flex-align($x: center, $y: middle);
|
||||
.user--name {
|
||||
@include margin(0);
|
||||
font-size: $font-size-medium;
|
||||
margin-left: $space-slab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button.resolve--button {
|
||||
> .icon {
|
||||
padding-right: $space-small;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
padding: 0 $space-one;
|
||||
margin-right: $space-smaller;
|
||||
&:before {
|
||||
border-top-color: $color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
.conversation {
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
@include padding($space-normal $zero $zero $space-normal);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background: $color-background;
|
||||
}
|
||||
|
||||
.conversation--details {
|
||||
@include margin($zero $zero $zero $space-one);
|
||||
@include border-light-bottom;
|
||||
@include padding($zero $zero $space-slab $zero);
|
||||
}
|
||||
.conversation--user {
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $zero;
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
top: $space-micro;
|
||||
left: $space-micro;
|
||||
max-width: $space-jumbo;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation--message {
|
||||
height: $space-medium;
|
||||
margin: $zero;
|
||||
font-size: $font-size-small;
|
||||
line-height: $space-medium;
|
||||
font-weight: $font-weight-light;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: $color-body;
|
||||
width: 27rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.conversation--meta {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: $space-normal;
|
||||
top: $space-normal;
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
|
||||
.unread {
|
||||
$unread-size: $space-two - $space-micro;
|
||||
display: none;
|
||||
height: $unread-size;
|
||||
min-width: $unread-size;
|
||||
background: darken($success-color, 3%);
|
||||
text-align: center;
|
||||
padding: 0 $space-smaller;
|
||||
line-height: $unread-size;
|
||||
color: $color-white;
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: $font-size-mini;
|
||||
margin-left: auto;
|
||||
@include round-corner;
|
||||
margin-top: $space-smaller;
|
||||
}
|
||||
.timestamp {
|
||||
font-size: $font-size-mini;
|
||||
color: $dark-gray;
|
||||
line-height: $space-normal;
|
||||
font-weight: $font-weight-normal;
|
||||
font-size: $font-size-micro;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.unread-chat {
|
||||
.unread {
|
||||
display: inline-block;
|
||||
}
|
||||
.conversation--message {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
.conversation--user {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
.conversations-sidebar {
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
|
||||
.chat-list__top {
|
||||
@include padding($space-normal $zero $space-small $zero);
|
||||
.page-title {
|
||||
float: left;
|
||||
margin-bottom: $zero;
|
||||
margin-left: $space-normal;
|
||||
}
|
||||
|
||||
.status--filter {
|
||||
@include padding($zero null $zero $space-normal);
|
||||
@include border-light;
|
||||
@include round-corner;
|
||||
@include margin($space-smaller $space-slab $zero $zero);
|
||||
background-color: $color-background;
|
||||
float: right;
|
||||
font-size: $font-size-mini;
|
||||
height: $space-medium;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
@include flex-weight(1);
|
||||
@include scroll-on-hover;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.emojione {
|
||||
height: $font-size-medium;
|
||||
width: $font-size-medium;
|
||||
}
|
||||
|
||||
.conversation-wrap {
|
||||
@include background-gray;
|
||||
@include margin(0);
|
||||
@include border-normal-left;
|
||||
.current-chat {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
div {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
img {
|
||||
@include margin($space-normal);
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conv-empty-state {
|
||||
@include flex;
|
||||
@include full-height;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-panel {
|
||||
@include flex;
|
||||
@include flex-weight(1);
|
||||
@include flex-direction(column);
|
||||
@include margin($zero);
|
||||
// Firefox flexbox fix
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
> li {
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
@include margin($zero $zero $space-smaller);
|
||||
|
||||
&:first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: $space-small;
|
||||
}
|
||||
|
||||
&.unread--toast {
|
||||
span {
|
||||
@include elegant-card;
|
||||
@include round-corner;
|
||||
background: $color-woot;
|
||||
color: $color-white;
|
||||
font-size: $font-size-mini;
|
||||
font-weight: $font-weight-medium;
|
||||
margin: $space-one auto;
|
||||
padding: $space-smaller $space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
max-width: 50rem;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
|
||||
.aplayer {
|
||||
box-shadow: none;
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&.left {
|
||||
.bubble {
|
||||
background: $white;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
color: $color-heading;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
+.right {
|
||||
margin-top: $space-one;
|
||||
|
||||
.bubble {
|
||||
border-top-right-radius: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.right {
|
||||
@include flex-align(right, null);
|
||||
|
||||
.wrap {
|
||||
margin-right: $space-small;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
margin-left: auto;
|
||||
|
||||
&.is-private {
|
||||
background: lighten($warning-color, 32%);
|
||||
color: $color-heading;
|
||||
padding-right: $space-large;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
bottom: 0;
|
||||
color: $medium-gray;
|
||||
position: absolute;
|
||||
right: $space-one;
|
||||
top: $space-smaller + $space-micro;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+.left {
|
||||
margin-top: $space-one;
|
||||
|
||||
.bubble {
|
||||
border-top-left-radius: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
@include margin($zero $space-normal);
|
||||
max-width: 69%;
|
||||
|
||||
.sender--name {
|
||||
font-size: $font-size-mini;
|
||||
margin-bottom: $space-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.sender--thumbnail {
|
||||
@include round-corner();
|
||||
height: $space-slab;
|
||||
margin-right: $space-one;
|
||||
margin-top: $space-micro;
|
||||
width: $space-slab;
|
||||
}
|
||||
|
||||
.activity-wrap {
|
||||
@include flex;
|
||||
@include margin($space-small auto);
|
||||
@include padding($space-smaller $space-normal);
|
||||
@include flex-align($x: center, $y: null);
|
||||
background: lighten($warning-color, 32%);
|
||||
border-radius: $space-smaller;
|
||||
font-size: $font-size-small;
|
||||
|
||||
p {
|
||||
color: $color-heading;
|
||||
margin-bottom: $zero;
|
||||
|
||||
.ion-person {
|
||||
color: $color-body;
|
||||
font-size: $font-size-default;
|
||||
margin-right: $space-small;
|
||||
position: relative;
|
||||
top: $space-micro;
|
||||
}
|
||||
|
||||
.message-text__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
&::after {
|
||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $medium-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
@include padding($space-smaller $space-one);
|
||||
@include margin($zero);
|
||||
background: #c7e3ff;
|
||||
border-radius: $space-small;
|
||||
box-shadow: 0 .5px .5px rgba(0, 0, 0, .05);
|
||||
color: $color-heading;
|
||||
font-size: $font-size-small;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
bottom: $space-smaller;
|
||||
position: absolute;
|
||||
right: $space-small;
|
||||
}
|
||||
|
||||
.message-text__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
&::after {
|
||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.audio {
|
||||
.time {
|
||||
margin-top: -$space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
@include flex;
|
||||
@include justify-content(center);
|
||||
@include align-items(flex-end);
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
margin-left: -$space-large;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-height: 80%;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
|
||||
img {
|
||||
@include padding($space-small);
|
||||
max-height: 30rem;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
@include padding($space-small);
|
||||
margin-left: -$space-smaller;
|
||||
margin-top: -$space-two;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.locname {
|
||||
font-weight: $font-weight-medium;
|
||||
padding: $space-smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
bottom: -$space-micro;
|
||||
color: $color-gray;
|
||||
float: right;
|
||||
font-size: $font-size-micro;
|
||||
font-style: italic;
|
||||
margin-left: $space-slab;
|
||||
position: absolute;
|
||||
right: -$space-micro;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
app/javascript/dashboard/assets/scss/widgets/_emojiinput.scss
Normal file
@@ -0,0 +1,106 @@
|
||||
.emoji-dialog {
|
||||
@include elegant-card;
|
||||
background: $color-white;
|
||||
border-radius: 2px;
|
||||
box-sizing: content-box;
|
||||
height: 20rem;
|
||||
padding-bottom: $space-two;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -22rem;
|
||||
width: 28.6rem;
|
||||
|
||||
&::before {
|
||||
@include arrow(bottom, $color-white, $space-slab);
|
||||
bottom: -$space-slab;
|
||||
position: absolute;
|
||||
right: $space-two;
|
||||
}
|
||||
|
||||
.emojione {
|
||||
@include margin($zero);
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.emoji-row {
|
||||
@include padding($space-small);
|
||||
box-sizing: border-box;
|
||||
height: 180px;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 0;
|
||||
|
||||
.emoji {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.emojione {
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
margin: .6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-category-title {
|
||||
color: $color-heading;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.emoji-category-heading-decoration {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-dialog-header {
|
||||
@include padding($zero $space-smaller);
|
||||
background-color: $light-gray;
|
||||
border-top-left-radius: $space-small;
|
||||
border-top-right-radius: $space-small;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: $space-smaller 0 0;
|
||||
|
||||
> li {
|
||||
@include padding($space-small $space-small);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 3.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
> .active {
|
||||
background: $white;
|
||||
border-top-left-radius: $space-small;
|
||||
border-top-right-radius: $space-small;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
img,
|
||||
svg {
|
||||
filter: grayscale(0);
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-dialog .emoji-row,
|
||||
.emoji-dialog .emoji-row .emoji:hover {
|
||||
background: $color-extra-light-blue;
|
||||
}
|
||||
31
app/javascript/dashboard/assets/scss/widgets/_forms.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
.error {
|
||||
#{$all-text-inputs},
|
||||
.multiselect > .multiselect__tags {
|
||||
@include thin-border( darken(get-color(alert), 25%));
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
.message {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: -$space-normal;
|
||||
margin-bottom: $space-one;
|
||||
color: darken(get-color(alert), 25%);
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.button,
|
||||
textarea,
|
||||
input {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
font-size: $font-size-small;
|
||||
color: $color-heading;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
64
app/javascript/dashboard/assets/scss/widgets/_login.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
.auth-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Outside login wrapper
|
||||
.login {
|
||||
@include full-height;
|
||||
overflow-y: scroll;
|
||||
padding-top: $space-larger * 1.2;
|
||||
|
||||
.login__hero {
|
||||
margin-bottom: $space-larger;
|
||||
|
||||
.hero__logo {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-weight: $font-weight-light;
|
||||
margin-top: $space-larger;
|
||||
}
|
||||
|
||||
.hero__sub {
|
||||
color: $medium-gray;
|
||||
font-size: $font-size-medium;
|
||||
}
|
||||
}
|
||||
|
||||
// Login box
|
||||
.login-box {
|
||||
@include background-white;
|
||||
@include border-normal;
|
||||
@include border-top-radius($space-smaller);
|
||||
@include border-right-radius($space-smaller);
|
||||
@include border-bottom-radius($space-smaller);
|
||||
@include border-left-radius($space-smaller);
|
||||
@include elegant-card;
|
||||
padding: $space-large;
|
||||
|
||||
label {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-default;
|
||||
|
||||
input {
|
||||
padding: $space-slab;
|
||||
height: $space-larger;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sigin__footer {
|
||||
font-size: $font-size-default;
|
||||
padding: $space-medium;
|
||||
|
||||
> a {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
app/javascript/dashboard/assets/scss/widgets/_modal.scss
Normal file
@@ -0,0 +1,92 @@
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9990;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
@include flex;
|
||||
@include flex-align(center, middle);
|
||||
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
width: 60rem;
|
||||
max-height: 100%;
|
||||
overflow: scroll;
|
||||
position: relative;
|
||||
background-color: $color-white;
|
||||
|
||||
.modal--close {
|
||||
font-size: $font-size-large;
|
||||
position: absolute;
|
||||
right: $space-normal;
|
||||
top: $space-small;
|
||||
cursor: pointer;
|
||||
color: $color-heading;
|
||||
}
|
||||
|
||||
.page-top-bar {
|
||||
background: $color-modal-header;
|
||||
text-align: center;
|
||||
@include padding($space-large $space-medium);
|
||||
img {
|
||||
max-height: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content-box {
|
||||
@include padding($zero);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
font-size: $font-size-medium;
|
||||
color: $color-woot;
|
||||
font-weight: $font-weight-normal;
|
||||
@include padding($space-small $zero $zero $zero);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $font-size-small;
|
||||
@include padding($zero);
|
||||
@include margin($zero);
|
||||
}
|
||||
|
||||
form {
|
||||
align-self: center;
|
||||
@include padding($space-medium $space-larger $space-small);
|
||||
a {
|
||||
@include padding($space-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: center);
|
||||
@include padding($space-small $zero $space-medium $zero);
|
||||
button {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-item {
|
||||
@include padding($space-normal);
|
||||
button {
|
||||
@include margin($zero);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal-enter, .modal-leave {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-enter .modal-container,
|
||||
.modal-leave .modal-container {
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
143
app/javascript/dashboard/assets/scss/widgets/_reply-box.scss
Normal file
@@ -0,0 +1,143 @@
|
||||
.reply-box {
|
||||
margin: $space-normal;
|
||||
margin-top: 0;
|
||||
border-bottom: 0;
|
||||
@include elegant-card;
|
||||
@include transition(height 2s $ease-in-out-cubic);
|
||||
max-height: $space-jumbo*2;
|
||||
|
||||
.reply-box__top {
|
||||
@include flex;
|
||||
@include flex-align($x: left, $y: middle);
|
||||
@include padding($space-one $space-normal);
|
||||
@include background-white;
|
||||
@include margin(0);
|
||||
position: relative;
|
||||
border-top-left-radius: $space-small;
|
||||
border-top-right-radius: $space-small;
|
||||
|
||||
.canned {
|
||||
@include elegant-card;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
width: 24rem;
|
||||
left: 0;
|
||||
border-top: $space-small solid $color-white;
|
||||
border-bottom: $space-small solid $color-white;
|
||||
max-height: 14rem;
|
||||
overflow: scroll;
|
||||
.active {
|
||||
a {
|
||||
background: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
border-bottom-left-radius: $space-small;
|
||||
border-bottom-right-radius: $space-small;
|
||||
}
|
||||
|
||||
&.is-private {
|
||||
background: lighten($warning-color, 38%);
|
||||
|
||||
> input {
|
||||
background: lighten($warning-color, 38%);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
font-size: $font-size-medium;
|
||||
color: $medium-gray;
|
||||
margin-right: $space-small;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
> textarea {
|
||||
@include ghost-input();
|
||||
@include margin(0);
|
||||
resize: none;
|
||||
background: transparent;
|
||||
// Override min-height : 50px in foundation
|
||||
//
|
||||
min-height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-box__bottom {
|
||||
@include background-light;
|
||||
@include flex;
|
||||
@include flex-align($x: justify, $y: middle);
|
||||
@include border-light-top;
|
||||
border-bottom-left-radius: $space-small;
|
||||
border-bottom-right-radius: $space-small;
|
||||
|
||||
.tabs {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
|
||||
.tabs-title {
|
||||
margin: 0;
|
||||
@include transition(background .2s $ease-in-out-cubic);
|
||||
@include transition(color .2s $ease-in-out-cubic);
|
||||
|
||||
a {
|
||||
padding: $space-one $space-two;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-bottom-left-radius: $space-small;
|
||||
&.is-active {
|
||||
@include border-light-right;
|
||||
border-left: 0;
|
||||
a {
|
||||
border-bottom-left-radius: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-private {
|
||||
&.is-active {
|
||||
background: lighten($warning-color, 38%);
|
||||
a {
|
||||
border-bottom-color: darken($warning-color, 15%);
|
||||
color: darken($warning-color, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
@include background-white;
|
||||
margin-top: -1px;
|
||||
@include border-light-left;
|
||||
@include border-light-right;
|
||||
}
|
||||
|
||||
.message-length {
|
||||
float: right;
|
||||
a {
|
||||
font-size: $font-size-mini;
|
||||
}
|
||||
}
|
||||
.message-error {
|
||||
color: $input-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.send-button {
|
||||
height: 3.6rem;
|
||||
border-bottom-right-radius: $space-small;
|
||||
padding-top: $space-small;
|
||||
padding-right: $space-two;
|
||||
padding-left: $space-two;
|
||||
|
||||
.icon {
|
||||
margin-left: $space-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
app/javascript/dashboard/assets/scss/widgets/_report.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
.report-card {
|
||||
@include padding($space-normal $space-small $space-normal $space-two);
|
||||
@include margin($zero);
|
||||
@include background-light;
|
||||
cursor: pointer;
|
||||
@include custom-border-top(3px, transparent);
|
||||
&.active {
|
||||
@include custom-border-top(3px, $color-woot);
|
||||
@include background-white;
|
||||
.heading,
|
||||
.metric {
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
.heading {
|
||||
@include margin($zero);
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $color-heading;
|
||||
}
|
||||
.metric {
|
||||
font-size: $font-size-bigger;
|
||||
font-weight: $font-weight-feather;
|
||||
}
|
||||
.desc {
|
||||
@include margin($zero);
|
||||
font-size: $font-size-small;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.report-bar {
|
||||
@include margin(-1px $zero);
|
||||
@include background-white;
|
||||
@include border-light;
|
||||
@include padding($space-small $space-medium);
|
||||
.chart-container {
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
@include flex-align(center, middle);
|
||||
div {
|
||||
width: 100%;
|
||||
}
|
||||
.empty-state {
|
||||
@include margin($space-jumbo);
|
||||
font-size: $font-size-default;
|
||||
color: $color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.search {
|
||||
@include flex;
|
||||
@include flex-align($x: left, $y: middle);
|
||||
@include padding($space-one $space-normal);
|
||||
@include flex-shrink;
|
||||
@include transition(all .3s $ease-in-out-quad);
|
||||
> .icon {
|
||||
font-size: $font-size-medium;
|
||||
color: $medium-gray;
|
||||
}
|
||||
> input {
|
||||
@include ghost-input();
|
||||
@include margin(0);
|
||||
}
|
||||
}
|
||||
132
app/javascript/dashboard/assets/scss/widgets/_sidemenu.scss
Normal file
@@ -0,0 +1,132 @@
|
||||
.side-menu {
|
||||
i {
|
||||
min-width: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@include border-normal-right;
|
||||
@include background-white;
|
||||
@include full-height;
|
||||
@include margin(0);
|
||||
@include space-between-column;
|
||||
overflow-x: hidden;
|
||||
width: $nav-bar-width;
|
||||
z-index: 1024 - 1;
|
||||
|
||||
//logo
|
||||
.logo {
|
||||
img {
|
||||
@include padding($woot-logo-padding);
|
||||
// width: $woot-logo-width;
|
||||
// height: $woot-logo-height;
|
||||
}
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
a {
|
||||
border-radius: $space-smaller;
|
||||
color: $color-gray;
|
||||
font-size: $font-size-default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bottom-nav
|
||||
.bottom-nav {
|
||||
@include flex;
|
||||
@include space-between-column;
|
||||
@include padding($space-one $space-normal $space-one $space-one);
|
||||
@include flex-direction(column);
|
||||
@include border-normal-top;
|
||||
position: relative;
|
||||
|
||||
.dropdown-pane {
|
||||
@include elegant-card;
|
||||
@include border-light;
|
||||
left: 18%;
|
||||
top: -160%;
|
||||
visibility: visible;
|
||||
width: 80%;
|
||||
z-index: 999;
|
||||
|
||||
&::before {
|
||||
@include arrow(bottom, $color-white, $space-slab);
|
||||
bottom: -$space-slab;
|
||||
position: absolute;
|
||||
right: $space-slab;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
border-bottom: 2px solid $medium-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
@include flex-weight(1);
|
||||
@include scroll-on-hover;
|
||||
padding: 0 $space-medium - $space-one;
|
||||
|
||||
a {
|
||||
&::before {
|
||||
margin-right: $space-slab;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-medium;
|
||||
margin-top: $space-medium;
|
||||
|
||||
> span {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-title + ul > li > a {
|
||||
@include padding($space-micro null);
|
||||
color: $medium-gray;
|
||||
line-height: $global-lineheight;
|
||||
}
|
||||
|
||||
.current-user {
|
||||
@include flex;
|
||||
@include flex-direction(row);
|
||||
cursor: pointer;
|
||||
|
||||
.current-user--thumbnail {
|
||||
@include round-corner();
|
||||
height: $space-large;
|
||||
width: $space-large;
|
||||
}
|
||||
|
||||
.current-user--data {
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
|
||||
.current-user--name {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: 1;
|
||||
margin-bottom: $zero;
|
||||
margin-left: $space-one;
|
||||
margin-top: $space-micro;
|
||||
}
|
||||
|
||||
.current-user--role {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-mini;
|
||||
margin-bottom: $zero;
|
||||
margin-left: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
.current-user--options {
|
||||
font-size: $font-size-big;
|
||||
margin-bottom: auto;
|
||||
margin-left: auto;
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
46
app/javascript/dashboard/assets/scss/widgets/_snackbar.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
.ui-snackbar-container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
z-index: 9999;
|
||||
top: $space-normal;
|
||||
left: $space-normal;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ui-snackbar {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
min-width: 24rem;
|
||||
max-width: 40rem;
|
||||
min-height: 3rem;
|
||||
background-color: $woot-snackbar-bg;
|
||||
@include padding($space-slab $space-medium);
|
||||
@include border-top-radius($space-micro);
|
||||
@include border-right-radius($space-micro);
|
||||
@include border-bottom-radius($space-micro);
|
||||
@include border-left-radius($space-micro);
|
||||
margin-bottom: $space-small;
|
||||
|
||||
// box-shadow: 0 1px 3px alpha(black, 0.12), 0 1px 2px alpha(black, 0.24);
|
||||
}
|
||||
|
||||
.ui-snackbar-text {
|
||||
font-size: $font-size-small;
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.ui-snackbar-action {
|
||||
margin-left: auto;
|
||||
padding-left: 3rem;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: $font-size-small;
|
||||
text-transform: uppercase;
|
||||
color: $woot-snackbar-button;
|
||||
@include margin(0);
|
||||
@include padding(0);
|
||||
}
|
||||
}
|
||||
39
app/javascript/dashboard/assets/scss/widgets/_states.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
.loading-state {
|
||||
padding: $space-jumbo $space-smaller;
|
||||
.message {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: $color-gray;
|
||||
}
|
||||
.spinner {
|
||||
float: none;
|
||||
top: -$space-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
// EMPTY STATES
|
||||
.empty-state {
|
||||
padding: $space-jumbo $space-smaller;
|
||||
.title,
|
||||
.message {
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: $font-size-giga;
|
||||
font-weight: $font-weight-feather;
|
||||
}
|
||||
|
||||
.message {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: $space-medium;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.status-bar {
|
||||
@include flex;
|
||||
@include flex-direction(column);
|
||||
@include flex-align($x: center, $y: middle);
|
||||
background: lighten($warning-color, 36%);
|
||||
// @include elegant-card();
|
||||
@include margin($zero);
|
||||
@include padding($space-normal $space-smaller);
|
||||
|
||||
.message {
|
||||
font-weight: $font-weight-medium;
|
||||
margin-bottom: $zero;
|
||||
}
|
||||
|
||||
.button {
|
||||
@include margin($space-smaller $zero $zero);
|
||||
padding: $space-small $space-normal;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: lighten($alert-color, 30%);
|
||||
|
||||
.button {
|
||||
@include button-style($alert-color, darken($alert-color, 7%), $color-white);
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background: lighten($warning-color, 36%);
|
||||
}
|
||||
}
|
||||
54
app/javascript/dashboard/assets/scss/widgets/_tabs.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
.tabs {
|
||||
@include padding($zero $space-normal);
|
||||
@include border-normal-bottom;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
// Tab chat type
|
||||
.tab--chat-type {
|
||||
@include flex;
|
||||
|
||||
.tabs-title {
|
||||
a {
|
||||
font-size: $font-size-default;
|
||||
padding-bottom: $space-slab;
|
||||
padding-top: $space-slab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-title {
|
||||
@include margin($zero $space-one);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
a {
|
||||
color: darken($medium-gray, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include position(relative, 1px null null null);
|
||||
@include transition(all .15s $ease-in-out-cubic);
|
||||
border-bottom: 2px solid transparent;
|
||||
color: $medium-gray;
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
a {
|
||||
border-bottom-color: $color-woot;
|
||||
color: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/javascript/dashboard/assets/scss/widgets/_thumbnail.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.user-thumbnail-box {
|
||||
@include flex-shrink;
|
||||
position: relative;
|
||||
|
||||
.user-thumbnail {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.source-badge {
|
||||
bottom: -$space-micro / 2;
|
||||
height: $space-slab;
|
||||
position: absolute;
|
||||
right: $zero;
|
||||
width: $space-slab;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
table {
|
||||
font-size: $font-size-small;
|
||||
border-spacing: 0;
|
||||
thead {
|
||||
th {
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
td {
|
||||
@include padding($space-one $space-small);
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woot-table {
|
||||
|
||||
tr {
|
||||
.show-if-hover {
|
||||
opacity: 0;
|
||||
@include transition(all .2s $ease-in-out-cubic);
|
||||
}
|
||||
&:hover {
|
||||
.show-if-hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.agent-name {
|
||||
font-weight: $font-weight-medium;
|
||||
display: block;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.woot-thumbnail {
|
||||
border-radius: 50%;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
.button-wrapper {
|
||||
min-width: 20rem;
|
||||
@include flex;
|
||||
@include flex-align(left, null);
|
||||
@include flex-direction(row);
|
||||
}
|
||||
.button {
|
||||
@include margin($zero);
|
||||
}
|
||||
}
|
||||
177
app/javascript/dashboard/components/ChatList.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="conversations-sidebar medium-4 columns">
|
||||
<!-- <SearchBox></SearchBox> -->
|
||||
|
||||
<div class="chat-list__top">
|
||||
<h1 class="page-title">
|
||||
{{ getInboxName }}
|
||||
</h1>
|
||||
<chat-filter @statusFilterChange="getDataForStatusTab"></chat-filter>
|
||||
</div>
|
||||
|
||||
<chat-type-tabs
|
||||
:items="assigneeTabItems"
|
||||
:active-tab-index="activeAssigneeTab"
|
||||
class="tab--chat-type"
|
||||
@chatTabChange="getDataForTab"
|
||||
></chat-type-tabs>
|
||||
|
||||
<p
|
||||
v-if="!chatListLoading && !getChatsForTab(activeStatusTab).length"
|
||||
class="content-box"
|
||||
>
|
||||
{{ $t('CHAT_LIST.LIST.404') }}
|
||||
</p>
|
||||
|
||||
<div v-if="chatListLoading" class="text-center">
|
||||
<span class="spinner message"></span>
|
||||
</div>
|
||||
|
||||
<transition-group
|
||||
name="conversations-list"
|
||||
tag="div"
|
||||
class="conversations-list"
|
||||
>
|
||||
<conversation-card
|
||||
v-for="chat in getChatsForTab(activeStatusTab)"
|
||||
:key="chat.id"
|
||||
:chat="chat"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-env browser */
|
||||
/* eslint no-console: 0 */
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import ChatFilter from './widgets/conversation/ChatFilter';
|
||||
import ChatTypeTabs from './widgets/ChatTypeTabs';
|
||||
import ConversationCard from './widgets/conversation/ConversationCard';
|
||||
import timeMixin from '../mixins/time';
|
||||
import conversationMixin from '../mixins/conversations';
|
||||
import wootConstants from '../constants';
|
||||
|
||||
export default {
|
||||
mixins: [timeMixin, conversationMixin],
|
||||
props: ['conversationInbox', 'pageTitle'],
|
||||
data: () => ({
|
||||
chats: null,
|
||||
activeStatusTab: 0,
|
||||
activeAssigneeTab: 0,
|
||||
toggleType: true,
|
||||
allMessageType: 2,
|
||||
}),
|
||||
mounted() {
|
||||
this.$watch('$store.state.route', () => {
|
||||
if (this.$store.state.route.name !== 'inbox_conversation') {
|
||||
this.$store.dispatch('emptyAllConversations');
|
||||
this.fetchData();
|
||||
}
|
||||
});
|
||||
|
||||
this.$store.dispatch('emptyAllConversations');
|
||||
this.fetchData();
|
||||
this.$store.dispatch('fetchAgents');
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
chatLists: 'getAllConversations',
|
||||
mineChatsList: 'getMineChats',
|
||||
allChatList: 'getAllStatusChats',
|
||||
unAssignedChatsList: 'getUnAssignedChats',
|
||||
inboxesList: 'getInboxesList',
|
||||
chatListLoading: 'getChatListLoadingStatus',
|
||||
currentUserID: 'getCurrentUserID',
|
||||
activeInbox: 'getSelectedInbox',
|
||||
}),
|
||||
convStats() {
|
||||
const mineCount = this.mineChatsList.length;
|
||||
const unAssignedCount = this.unAssignedChatsList.length;
|
||||
const allCount = this.allChatList.length;
|
||||
return {
|
||||
mineCount,
|
||||
unAssignedCount,
|
||||
allCount,
|
||||
};
|
||||
},
|
||||
assigneeTabItems() {
|
||||
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map((item, index) => ({
|
||||
id: index,
|
||||
name: item.NAME,
|
||||
count: this.convStats[item.KEY] || 0,
|
||||
}));
|
||||
},
|
||||
getInboxName() {
|
||||
const inboxId = Number(this.activeInbox);
|
||||
const [stateInbox] = this.inboxesList.filter(
|
||||
inbox => inbox.channel_id === inboxId
|
||||
);
|
||||
return !stateInbox ? this.pageTitle : stateInbox.label;
|
||||
},
|
||||
getToggleStatus() {
|
||||
if (this.toggleType) {
|
||||
return 'Open';
|
||||
}
|
||||
return 'Resolved';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
if (this.chatLists.length === 0) {
|
||||
this.$store.dispatch('fetchAllConversations', {
|
||||
inbox: this.conversationInbox,
|
||||
assigneeStatus: this.allMessageType,
|
||||
convStatus: this.activeStatusTab,
|
||||
});
|
||||
}
|
||||
},
|
||||
getDataForTab(index) {
|
||||
this.activeAssigneeTab = index;
|
||||
if (!(index in this.chatLists)) {
|
||||
// this.$store.dispatch('fetchList', {
|
||||
// inbox: this.conversationInbox,
|
||||
// type: index,
|
||||
// });
|
||||
}
|
||||
},
|
||||
getDataForStatusTab(index) {
|
||||
this.activeStatusTab = index;
|
||||
},
|
||||
getChatsForTab() {
|
||||
let copyList = [];
|
||||
if (this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.MINE) {
|
||||
copyList = this.mineChatsList.slice();
|
||||
} else if (
|
||||
this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE_SLUG.UNASSIGNED
|
||||
) {
|
||||
copyList = this.unAssignedChatsList.slice();
|
||||
} else {
|
||||
copyList = this.allChatList.slice();
|
||||
}
|
||||
const sorted = copyList.sort((a, b) =>
|
||||
this.wootTime(this.lastMessage(a).created_at).isBefore(
|
||||
this.wootTime(this.lastMessage(b).created_at)
|
||||
)
|
||||
);
|
||||
|
||||
return sorted;
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
ChatTypeTabs,
|
||||
ConversationCard,
|
||||
ChatFilter,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~dashboard/assets/scss/variables';
|
||||
.spinner {
|
||||
margin-top: $space-normal;
|
||||
margin-bottom: $space-normal;
|
||||
}
|
||||
</style>
|
||||
33
app/javascript/dashboard/components/Modal.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<div class="modal-mask" @click="close" v-if="show" transition="modal">
|
||||
<div class="modal-container" :class="className" @click.stop>
|
||||
<i class="ion-android-close modal--close" @click="close"></i>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: Boolean,
|
||||
onClose: Function,
|
||||
className: String,
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.onClose();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (this.show && e.keyCode === 27) {
|
||||
this.onClose();
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
21
app/javascript/dashboard/components/ModalHeader.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="column page-top-bar">
|
||||
<img :src="headerImage" alt="No image" v-if="headerImage"/>
|
||||
<h2 class="page-sub-title">
|
||||
{{headerTitle}}
|
||||
</h2>
|
||||
<p class="small-12 column">
|
||||
{{headerContent}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: String,
|
||||
headerContent: String,
|
||||
headerImage: String,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
30
app/javascript/dashboard/components/Snackbar.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="ui-snackbar">
|
||||
<div class="ui-snackbar-text">{{ message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: String,
|
||||
showButton: Boolean,
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: 3000,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
toggleAfterTimeout: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
37
app/javascript/dashboard/components/SnackbarContainer.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<transition-group name="toast-fade" tag="div" class="ui-snackbar-container">
|
||||
<woot-snackbar :message="snackMessage" v-for="snackMessage in snackMessages" v-bind:key="snackMessage"></woot-snackbar>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global bus */
|
||||
import WootSnackbar from './Snackbar';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
duration: {
|
||||
default: 2500,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
snackMessages: [],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
bus.$on('newToastMessage', (message) => {
|
||||
this.snackMessages.push(message);
|
||||
window.setTimeout(() => {
|
||||
this.snackMessages.splice(0, 1);
|
||||
}, this.duration);
|
||||
});
|
||||
},
|
||||
|
||||
components: {
|
||||
WootSnackbar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
3
app/javascript/dashboard/components/Spinner.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<span class="spinner small"></span>
|
||||
</template>
|
||||
19
app/javascript/dashboard/components/Thumbnail.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="profile-card">
|
||||
<div class="profile-image" v-bind:style="{ height: size, width: size }">
|
||||
<img v-bind:src="src">
|
||||
</div>
|
||||
<img class="source-badge" src="../assets/images/fb-badge.png" v-if="badge === 'fb'">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Thumbnail Component
|
||||
* Src - source for round image
|
||||
* Size - Size of the thumbnail
|
||||
* Badge - Chat source indication { fb / telegram }
|
||||
*/
|
||||
export default {
|
||||
props: ['src', 'size', 'badge'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<button type="submit" :disabled="disabled" :class="computedClass">
|
||||
<i v-if="!!iconClass" :class="iconClass" class="icon"></i>
|
||||
<span>{{ buttonText }}</span>
|
||||
<spinner v-if="loading" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
export default {
|
||||
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 nice ${this.buttonClass || ' '}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
export default {
|
||||
props: ['conversationId'],
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
currentStatus() {
|
||||
const ButtonName = this.currentChat.status === 0 ? 'Resolve' : 'Reopen';
|
||||
return ButtonName;
|
||||
},
|
||||
buttonClass() {
|
||||
return this.currentChat.status === 0 ? 'success' : 'warning';
|
||||
},
|
||||
buttonIconClass() {
|
||||
return this.currentChat.status === 0 ? 'ion-checkmark' : 'ion-refresh';
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
methods: {
|
||||
toggleStatus() {
|
||||
this.isLoading = true;
|
||||
this.$store.dispatch('toggleStatus', this.currentChat.id).then(() => {
|
||||
bus.$emit('newToastMessage', this.$t('CONVERSATION.CHANGE_STATUS'));
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
40
app/javascript/dashboard/components/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint no-plusplus: 0 */
|
||||
/* eslint-env browser */
|
||||
|
||||
import Modal from './Modal';
|
||||
import Thumbnail from './Thumbnail';
|
||||
import Spinner from './Spinner';
|
||||
import SubmitButton from './buttons/FormSubmitButton';
|
||||
import Tabs from './ui/Tabs/Tabs';
|
||||
import TabsItem from './ui/Tabs/TabsItem';
|
||||
import LoadingState from './widgets/LoadingState';
|
||||
import ReportStatsCard from './widgets/ReportStatsCard';
|
||||
import Bar from './widgets/chart/BarChart';
|
||||
import ModalHeader from './ModalHeader';
|
||||
|
||||
const WootUIKit = {
|
||||
Modal,
|
||||
Thumbnail,
|
||||
Spinner,
|
||||
SubmitButton,
|
||||
Tabs,
|
||||
TabsItem,
|
||||
LoadingState,
|
||||
ReportStatsCard,
|
||||
Bar,
|
||||
ModalHeader,
|
||||
install(Vue) {
|
||||
const keys = Object.keys(this);
|
||||
keys.pop(); // remove 'install' from keys
|
||||
let i = keys.length;
|
||||
while (i--) {
|
||||
Vue.component(`woot${keys[i]}`, this[keys[i]]);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.Vue.use(WootUIKit);
|
||||
}
|
||||
|
||||
export default WootUIKit;
|
||||
159
app/javascript/dashboard/components/layout/Sidebar.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<aside class="sidebar animated shrink columns">
|
||||
<div class="logo">
|
||||
<router-link :to="dashboardPath" replace>
|
||||
<img src="~dashboard/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 accessibleMenuItems"
|
||||
:key="item.toState"
|
||||
:menu-item="item"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
<transition name="fade" mode="out-in">
|
||||
<woot-status-bar
|
||||
v-if="shouldShowStatusBox"
|
||||
:message="trialMessage"
|
||||
:button-text="$t('APP_GLOBAL.TRAIL_BUTTON')"
|
||||
:button-route="{ name: 'billing' }"
|
||||
:type="statusBarClass"
|
||||
:show-button="isAdmin()"
|
||||
/>
|
||||
</transition>
|
||||
<div class="bottom-nav">
|
||||
<transition name="menu-slide">
|
||||
<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="#" @click.prevent="logout()">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
<div class="current-user" @click.prevent="showOptions()">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import md5 from 'md5';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import adminMixin from '../../mixins/isAdmin';
|
||||
import Auth from '../../api/auth';
|
||||
import SidebarItem from './SidebarItem';
|
||||
import WootStatusBar from '../widgets/StatusBar';
|
||||
import { frontendURL } from '../../helper/URLHelper';
|
||||
|
||||
export default {
|
||||
mixins: [clickaway, adminMixin],
|
||||
props: {
|
||||
route: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showOptionsMenu: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// this.$store.dispatch('fetchLabels');
|
||||
this.$store.dispatch('fetchInboxes');
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
sidebarList: 'getMenuItems',
|
||||
daysLeft: 'getTrialLeft',
|
||||
subscriptionData: 'getSubscription',
|
||||
}),
|
||||
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) {
|
||||
menuItems = Object.values(groupItem.menuItems);
|
||||
}
|
||||
}
|
||||
|
||||
const { role } = this.currentUser;
|
||||
return menuItems.filter(
|
||||
menuItem =>
|
||||
window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
|
||||
);
|
||||
},
|
||||
dashboardPath() {
|
||||
return frontendURL('dashboard');
|
||||
},
|
||||
currentUser() {
|
||||
return Auth.getCurrentUser();
|
||||
},
|
||||
trialMessage() {
|
||||
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
|
||||
},
|
||||
shouldShowStatusBox() {
|
||||
return (
|
||||
this.subscriptionData.state === 'trial' ||
|
||||
this.subscriptionData.state === 'cancelled'
|
||||
);
|
||||
},
|
||||
statusBarClass() {
|
||||
if (this.subscriptionData.state === 'trial') {
|
||||
return 'warning';
|
||||
}
|
||||
if (this.subscriptionData.state === 'cancelled') {
|
||||
return 'danger';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
logout() {
|
||||
Auth.logout();
|
||||
},
|
||||
gravatarUrl() {
|
||||
const hash = md5(this.currentUser.email);
|
||||
return `${window.WootConstants.GRAVATAR_URL}${hash}?d=monsterid`;
|
||||
},
|
||||
showOptions() {
|
||||
this.showOptionsMenu = !this.showOptionsMenu;
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
SidebarItem,
|
||||
WootStatusBar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
91
app/javascript/dashboard/components/layout/SidebarItem.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<router-link
|
||||
:to="menuItem.toState"
|
||||
tag="li"
|
||||
active-class="active"
|
||||
:class="computedClass"
|
||||
>
|
||||
<a
|
||||
:class="getMenuItemClass"
|
||||
data-tooltip
|
||||
aria-haspopup="true"
|
||||
:title="menuItem.toolTip"
|
||||
>
|
||||
<i :class="menuItem.icon" />
|
||||
{{ menuItem.label }}
|
||||
<span
|
||||
v-if="showItem(menuItem)"
|
||||
class="ion-ios-plus-outline"
|
||||
@click.prevent="newLinkClick"
|
||||
/>
|
||||
</a>
|
||||
<ul v-if="menuItem.hasSubMenu" class="nested vertical menu">
|
||||
<router-link
|
||||
v-for="child in menuItem.children"
|
||||
:key="child.label"
|
||||
active-class="active flex-container"
|
||||
:class="computedInboxClass(child)"
|
||||
tag="li"
|
||||
:to="child.toState"
|
||||
>
|
||||
<a>{{ child.label }}</a>
|
||||
</router-link>
|
||||
</ul>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import router from '../../routes';
|
||||
import auth from '../../api/auth';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
menuItem: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
activeInbox: 'getSelectedInbox',
|
||||
}),
|
||||
getMenuItemClass() {
|
||||
return this.menuItem.cssClass
|
||||
? `side-menu ${this.menuItem.cssClass}`
|
||||
: 'side-menu';
|
||||
},
|
||||
computedClass() {
|
||||
// If active Inbox is present
|
||||
// donot highlight conversations
|
||||
if (this.activeInbox) return ' ';
|
||||
|
||||
if (
|
||||
this.$store.state.route.name === 'inbox_conversation' &&
|
||||
this.menuItem.toStateName === 'home'
|
||||
) {
|
||||
return 'active';
|
||||
}
|
||||
return ' ';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
computedInboxClass(child) {
|
||||
if (parseInt(this.activeInbox, 10) === child.channel_id) {
|
||||
return 'active flex-container';
|
||||
}
|
||||
return ' ';
|
||||
},
|
||||
newLinkClick() {
|
||||
router.push({ name: 'settings_inbox_new', params: { page: 'new' } });
|
||||
},
|
||||
showItem(item) {
|
||||
return auth.isAdmin() && item.newLink !== undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
49
app/javascript/dashboard/components/ui/Switch.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
<template>
|
||||
<label class="switch" :class="classObject">
|
||||
<input class="switch-input" :name="name" :id="id" :disabled="disabled" v-model="value" type="checkbox">
|
||||
<div class="switch-paddle" :for="name">
|
||||
<span class="show-for-sr">on off</span>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
isFullwidth: Boolean,
|
||||
type: String,
|
||||
size: String,
|
||||
checked: Boolean,
|
||||
name: String,
|
||||
id: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: null,
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.value = this.checked;
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('input', this.value = !!this.checked);
|
||||
},
|
||||
computed: {
|
||||
classObject() {
|
||||
const { type, size, value } = this;
|
||||
return {
|
||||
[`is-${type}`]: type,
|
||||
[`${size}`]: size,
|
||||
checked: value,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
33
app/javascript/dashboard/components/ui/Tabs/Tabs.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint no-unused-vars: ["error", { "args": "none" }] */
|
||||
|
||||
export default {
|
||||
name: 'WootTabs',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
const Tabs = this.$slots.default
|
||||
.filter(
|
||||
node =>
|
||||
node.componentOptions &&
|
||||
node.componentOptions.tag === 'woot-tabs-item'
|
||||
)
|
||||
.map((node, index) => {
|
||||
const data = node.componentOptions.propsData;
|
||||
data.index = index;
|
||||
return node;
|
||||
});
|
||||
return (
|
||||
<ul
|
||||
class={{
|
||||
tabs: true,
|
||||
}}
|
||||
>
|
||||
{Tabs}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
};
|
||||
88
app/javascript/dashboard/components/ui/Tabs/TabsItem.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint no-unused-vars: ["error", { "args": "none" }] */
|
||||
/* eslint prefer-template: 0 */
|
||||
/* eslint no-console: 0 */
|
||||
/* eslint func-names: 0 */
|
||||
import TWEEN from 'tween.js';
|
||||
|
||||
export default {
|
||||
name: 'WootTabsItem',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
animatedNumber: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
active() {
|
||||
return this.index === this.$parent.index;
|
||||
},
|
||||
|
||||
getItemCount() {
|
||||
return this.animatedNumber || this.count;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
count(newValue, oldValue) {
|
||||
let animationFrame;
|
||||
const animate = time => {
|
||||
TWEEN.update(time);
|
||||
animationFrame = window.requestAnimationFrame(animate);
|
||||
};
|
||||
const that = this;
|
||||
new TWEEN.Tween({ tweeningNumber: oldValue })
|
||||
.easing(TWEEN.Easing.Quadratic.Out)
|
||||
.to({ tweeningNumber: newValue }, 500)
|
||||
.onUpdate(function() {
|
||||
that.animatedNumber = this.tweeningNumber.toFixed(0);
|
||||
})
|
||||
.onComplete(() => {
|
||||
window.cancelAnimationFrame(animationFrame);
|
||||
})
|
||||
.start();
|
||||
animationFrame = window.requestAnimationFrame(animate);
|
||||
},
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return (
|
||||
<li
|
||||
class={{
|
||||
'tabs-title': true,
|
||||
'is-active': this.active,
|
||||
'uk-disabled': this.disabled,
|
||||
}}
|
||||
>
|
||||
<a
|
||||
on-click={event => {
|
||||
event.preventDefault();
|
||||
if (!this.disabled) {
|
||||
this.$parent.$emit('change', this.index);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{`${this.name} (${this.getItemCount})`}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
};
|
||||
58
app/javascript/dashboard/components/ui/Wizard.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<transition-group
|
||||
name="wizard-items"
|
||||
tag="div"
|
||||
class="wizard-box flex-child-shrink"
|
||||
:class="classObject"
|
||||
>
|
||||
<div
|
||||
v-for="item in items"
|
||||
:key="item.route"
|
||||
class="item"
|
||||
:class="{ active: isActive(item), over: isOver(item) }"
|
||||
>
|
||||
<h3>
|
||||
{{ item.title }}
|
||||
<span v-if="isOver(item)" class="completed">
|
||||
<i class="ion-checkmark"></i>
|
||||
</span>
|
||||
</h3>
|
||||
<span class="step">
|
||||
{{ items.indexOf(item) + 1 }}
|
||||
</span>
|
||||
<p>{{ item.body }}</p>
|
||||
</div>
|
||||
</transition-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
isFullwidth: Boolean,
|
||||
},
|
||||
|
||||
computed: {
|
||||
classObject() {
|
||||
return 'full-width';
|
||||
},
|
||||
activeIndex() {
|
||||
return this.items.findIndex(i => i.route === this.$route.name);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(item) {
|
||||
return this.items.indexOf(item) === this.activeIndex;
|
||||
},
|
||||
isOver(item) {
|
||||
return this.items.indexOf(item) < this.activeIndex;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
14
app/javascript/dashboard/components/widgets/BackButton.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<span class="back-button ion-ios-arrow-left" @click.capture="goBack">Back</span>
|
||||
</template>
|
||||
<script>
|
||||
import router from '../../routes/index';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
goBack() {
|
||||
router.go(-1);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
23
app/javascript/dashboard/components/widgets/ChannelItem.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="small-3 columns channel" :class="{ inactive: channel !== 'facebook' }" @click.capture="itemClick">
|
||||
<img src="~dashboard/assets/images/channels/facebook.png" v-if="channel === 'facebook'">
|
||||
<img src="~dashboard/assets/images/channels/twitter.png" v-if="channel === 'twitter'">
|
||||
<img src="~dashboard/assets/images/channels/telegram.png" v-if="channel === 'telegram'">
|
||||
<img src="~dashboard/assets/images/channels/line.png" v-if="channel === 'line'">
|
||||
<h3 class="channel__title">{{channel}}</h3>
|
||||
<!-- <p>This is the most sexiest integration to begin </p> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* global bus */
|
||||
export default {
|
||||
props: ['channel'],
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
itemClick() {
|
||||
bus.$emit('channelItemClick', this.channel);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
40
app/javascript/dashboard/components/widgets/ChatTypeTabs.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<woot-tabs :index="tabsIndex" @change="onTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="item in items"
|
||||
:key="item.name"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
activeTabIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabsIndex: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.tabsIndex = this.activeTabIndex;
|
||||
},
|
||||
methods: {
|
||||
onTabChange(selectedTabIndex) {
|
||||
this.$emit('chatTabChange', selectedTabIndex);
|
||||
this.tabsIndex = selectedTabIndex;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
17
app/javascript/dashboard/components/widgets/EmptyState.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="row empty-state">
|
||||
<h3 class="title">{{title}}</h3>
|
||||
<p class="message">{{message}}</p>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
message: String,
|
||||
buttonText: String,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="inbox-item" >
|
||||
<img src="~dashboard/assets/images/no_page_image.png" alt="No Page Image"/>
|
||||
<div class="item--details columns">
|
||||
<h4 class="item--name">{{ inbox.label }}</h4>
|
||||
<p class="item--sub">Facebook</p>
|
||||
</div>
|
||||
<!-- <span class="ion-chevron-right arrow"></span> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
/* global bus */
|
||||
// import WootSwitch from '../ui/Switch';
|
||||
|
||||
export default {
|
||||
props: ['inbox'],
|
||||
created() {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
12
app/javascript/dashboard/components/widgets/LoadingState.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="row loading-state">
|
||||
<h6 class="message">{{message}}<span class="spinner"></span></h6>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: String,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="small-2 report-card" :class="{ 'active': selected }" v-on:click="onClick(index)">
|
||||
<h3 class="heading">{{heading}}</h3>
|
||||
<h4 class="metric">{{point}}</h4>
|
||||
<p class="desc">{{desc}}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
heading: String,
|
||||
point: [Number, String],
|
||||
index: Number,
|
||||
desc: String,
|
||||
selected: Boolean,
|
||||
onClick: Function,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
11
app/javascript/dashboard/components/widgets/SearchBox.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<i class="icon ion-ios-search-strong"></i>
|
||||
<input class="input" type="email" v-bind:placeholder="$t('CHAT_LIST.SEARCH.INPUT')">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
};
|
||||
</script>
|
||||
24
app/javascript/dashboard/components/widgets/StatusBar.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="status-bar" :class="type">
|
||||
<p class="message">{{message}}</p>
|
||||
<router-link
|
||||
:to="buttonRoute"
|
||||
class="button small warning nice"
|
||||
v-if="showButton"
|
||||
>
|
||||
{{buttonText}}
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
message: String,
|
||||
buttonRoute: Object,
|
||||
buttonText: String,
|
||||
showButton: Boolean,
|
||||
type: String, // Danger, Info, Success, Warning
|
||||
},
|
||||
};
|
||||
</script>
|
||||
29
app/javascript/dashboard/components/widgets/Thumbnail.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="user-thumbnail-box" v-bind:style="{ height: size, width: size }">
|
||||
<img v-bind:src="src" class="user-thumbnail">
|
||||
<img class="source-badge" src="~dashboard/assets/images/fb-badge.png" v-if="badge === 'Facebook'">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Thumbnail Component
|
||||
* Src - source for round image
|
||||
* Size - Size of the thumbnail
|
||||
* Badge - Chat source indication { fb / telegram }
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '40px',
|
||||
},
|
||||
badge: {
|
||||
type: String,
|
||||
default: 'fb',
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Bar } from 'vue-chartjs';
|
||||
|
||||
const fontFamily =
|
||||
'-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
||||
|
||||
export default Bar.extend({
|
||||
props: ['collection'],
|
||||
mounted() {
|
||||
this.renderChart(this.collection, {
|
||||
// responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
labels: {
|
||||
fontFamily,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
barPercentage: 1.9,
|
||||
ticks: {
|
||||
fontFamily,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
fontFamily,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="vertical dropdown menu canned" id="canned-list" v-bind:style="{ top: getTopPadding() + 'rem'}">
|
||||
<li
|
||||
v-for="(item, index) in cannedMessages"
|
||||
:id="`canned-${index}`"
|
||||
:class="{'active': index === selectedIndex}"
|
||||
v-on:click="onListItemSelection(index)"
|
||||
v-on:mouseover="onHover(index)"
|
||||
>
|
||||
<a><strong>{{item.short_code}}</strong> - {{item.content}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: ['onKeyenter', 'onClick'],
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
cannedMessages: 'getCannedResponses',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
/* eslint-disable no-confusing-arrow */
|
||||
document.addEventListener('keydown', this.keyListener);
|
||||
},
|
||||
methods: {
|
||||
getTopPadding() {
|
||||
if (this.cannedMessages.length <= 4) {
|
||||
return -this.cannedMessages.length * 3.5;
|
||||
}
|
||||
return -14;
|
||||
},
|
||||
isUp(e) {
|
||||
return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P
|
||||
},
|
||||
isDown(e) {
|
||||
return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N
|
||||
},
|
||||
isEnter(e) {
|
||||
return e.keyCode === 13;
|
||||
},
|
||||
keyListener(e) {
|
||||
if (this.isUp(e)) {
|
||||
if (!this.selectedIndex) {
|
||||
this.selectedIndex = this.cannedMessages.length - 1;
|
||||
} else {
|
||||
this.selectedIndex -= 1;
|
||||
}
|
||||
}
|
||||
if (this.isDown(e)) {
|
||||
if (this.selectedIndex === this.cannedMessages.length - 1) {
|
||||
this.selectedIndex = 0;
|
||||
} else {
|
||||
this.selectedIndex += 1;
|
||||
}
|
||||
}
|
||||
if (this.isEnter(e)) {
|
||||
this.onKeyenter(this.cannedMessages[this.selectedIndex].content);
|
||||
}
|
||||
this.$el.querySelector('#canned-list').scrollTop = 34 * this.selectedIndex;
|
||||
},
|
||||
onHover(index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
onListItemSelection(index) {
|
||||
this.selectedIndex = index;
|
||||
this.onClick(this.cannedMessages[this.selectedIndex].content);
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.keyListener);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
<template>
|
||||
<select class="status--filter" v-model="activeIndex" @change="onTabChange()">
|
||||
<option v-for="(item, index) in $t('CHAT_LIST.CHAT_STAUTUS_ITEMS')" :value="item['VALUE']">{{item["TEXT"]}}</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({
|
||||
activeIndex: 0,
|
||||
}),
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
onTabChange() {
|
||||
this.$store.dispatch('setChatFilter', this.activeIndex);
|
||||
this.$emit('statusFilterChange', this.activeIndex);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<li :class="alignBubble" v-if="data.attachment || data.content">
|
||||
<div :class="wrapClass">
|
||||
<p
|
||||
:class="{ bubble: isBubble, 'is-private': isPrivate }"
|
||||
v-tooltip.top-start="sentByMessage"
|
||||
>
|
||||
<bubble-image
|
||||
:url="data.attachment.data_url"
|
||||
:readable-time="readableTime"
|
||||
v-if="data.attachment && data.attachment.file_type==='image'"
|
||||
/>
|
||||
<bubble-audio
|
||||
:url="data.attachment.data_url"
|
||||
:readable-time="readableTime"
|
||||
v-if="data.attachment && data.attachment.file_type==='audio'"
|
||||
/>
|
||||
<bubble-map
|
||||
:lat="data.attachment.coordinates_lat"
|
||||
:lng="data.attachment.coordinates_long"
|
||||
:label="data.attachment.fallback_title"
|
||||
:readable-time="readableTime"
|
||||
v-if="data.attachment && data.attachment.file_type==='location'"
|
||||
/>
|
||||
<i class="icon ion-person" v-if="data.message_type === 2"></i>
|
||||
<bubble-text v-if="data.content" :message="message" :readable-time="readableTime"/>
|
||||
<i class="icon ion-android-lock" v-if="isPrivate" v-tooltip.top-start="toolTipMessage" @mouseenter="isHovered = true" @mouseleave="isHovered = false"></i>
|
||||
</p>
|
||||
</div>
|
||||
<!-- <img v-if="showSenderData" src="https://chatwoot-staging.s3-us-west-2.amazonaws.com/uploads/avatar/contact/3415/thumb_10418362_10201264050880840_6087258728802054624_n.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAI3KBM2ES3VRHQHPQ%2F20170422%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20170422T075421Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=8d5ff60e41415515f59ff682b9a4e4c0574d9d9aabfeff1dc5a51087a9b49e03" class="sender--thumbnail"> -->
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint-disable no-named-as-default */
|
||||
import getEmojiSVG from '../emoji/utils';
|
||||
import timeMixin from '../../../mixins/time';
|
||||
import BubbleText from './bubble/Text';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleMap from './bubble/Map';
|
||||
import BubbleAudio from './bubble/Audio';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BubbleText,
|
||||
BubbleImage,
|
||||
BubbleMap,
|
||||
BubbleAudio,
|
||||
},
|
||||
props: ['data'],
|
||||
mixins: [timeMixin],
|
||||
data() {
|
||||
return {
|
||||
isHovered: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
message() {
|
||||
const linkifiedMessage = this.linkify(this.data.content);
|
||||
return linkifiedMessage.replace(/\n/g, '<br>');
|
||||
},
|
||||
alignBubble() {
|
||||
return this.data.message_type === 1 ? 'right' : 'left';
|
||||
},
|
||||
readableTime() {
|
||||
return this.messageStamp(this.data.created_at);
|
||||
},
|
||||
isBubble() {
|
||||
return this.data.message_type === 1 || this.data.message_type === 0;
|
||||
},
|
||||
isPrivate() {
|
||||
return this.data.private;
|
||||
},
|
||||
toolTipMessage() {
|
||||
return this.data.private ? { content: this.$t('CONVERSATION.VISIBLE_TO_AGENTS'), classes: 'top' } : false;
|
||||
},
|
||||
sentByMessage() {
|
||||
return this.data.message_type === 1 && !this.isHovered && this.data.sender !== undefined ?
|
||||
{ content: `Sent by: ${this.data.sender.name}`, classes: 'top' } : false;
|
||||
},
|
||||
wrapClass() {
|
||||
return {
|
||||
wrap: this.isBubble,
|
||||
'activity-wrap': !this.isBubble,
|
||||
};
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
getEmojiSVG,
|
||||
linkify(text) {
|
||||
if (!text) return text;
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
return text.replace(urlRegex, url => `<a href="${url}" target="_blank">${url}</a>`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<div class="medium-8 columns conversation-wrap">
|
||||
<div v-if="currentChat.id !== null" class="view-box columns">
|
||||
<conversation-header :chat="currentChat" />
|
||||
<ul class="conversation-panel">
|
||||
<transition name="slide-up">
|
||||
<li>
|
||||
<span v-if="shouldShowSpinner" class="spinner message" />
|
||||
</li>
|
||||
</transition>
|
||||
<conversation
|
||||
v-for="message in getReadMessages"
|
||||
:key="message.id"
|
||||
:data="message"
|
||||
/>
|
||||
<li v-show="getUnreadCount != 0" class="unread--toast">
|
||||
<span>
|
||||
{{ getUnreadCount }} UNREAD MESSAGE{{
|
||||
getUnreadCount > 1 ? 'S' : ''
|
||||
}}
|
||||
</span>
|
||||
</li>
|
||||
<conversation
|
||||
v-for="message in getUnReadMessages"
|
||||
:key="message.id"
|
||||
:data="message"
|
||||
/>
|
||||
</ul>
|
||||
<ReplyBox
|
||||
:conversation-id="currentChat.id"
|
||||
@scrollToMessage="focusLastMessage"
|
||||
/>
|
||||
</div>
|
||||
<!-- No Conversation Selected -->
|
||||
<div class="columns full-height conv-empty-state">
|
||||
<!-- Loading status -->
|
||||
<woot-loading-state
|
||||
v-if="fetchingInboxes || loadingChatList"
|
||||
:message="loadingIndicatorMessage"
|
||||
/>
|
||||
<!-- Show empty state images if not loading -->
|
||||
<div v-if="!fetchingInboxes && !loadingChatList" class="current-chat">
|
||||
<!-- No inboxes attached -->
|
||||
<div v-if="!inboxesList.length">
|
||||
<img src="~dashboard/assets/images/inboxes.svg" alt="No Inboxes" />
|
||||
<span v-if="isAdmin()">
|
||||
{{ $t('CONVERSATION.NO_INBOX_1') }}
|
||||
<br />
|
||||
<router-link :to="newInboxURL">
|
||||
{{ $t('CONVERSATION.CLICK_HERE') }}
|
||||
</router-link>
|
||||
{{ $t('CONVERSATION.NO_INBOX_2') }}
|
||||
</span>
|
||||
<span v-if="!isAdmin()">
|
||||
{{ $t('CONVERSATION.NO_INBOX_AGENT') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- No conversations available -->
|
||||
<div v-else-if="!allConversations.length">
|
||||
<img src="~dashboard/assets/images/chat.svg" alt="No Chat" />
|
||||
<span>
|
||||
{{ $t('CONVERSATION.NO_MESSAGE_1') }}
|
||||
<br />
|
||||
<a :href="linkToMessage" target="_blank">
|
||||
{{ $t('CONVERSATION.CLICK_HERE') }}
|
||||
</a>
|
||||
{{ $t('CONVERSATION.NO_MESSAGE_2') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- No conversation selected -->
|
||||
<div v-else-if="allConversations.length && currentChat.id === null">
|
||||
<img src="~dashboard/assets/images/chat.svg" alt="No Chat" />
|
||||
<span>{{ $t('CONVERSATION.404') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
/* eslint no-extra-boolean-cast: 0 */
|
||||
/* global bus */
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import ConversationHeader from './ConversationHeader';
|
||||
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: {
|
||||
ConversationHeader,
|
||||
Conversation,
|
||||
ReplyBox,
|
||||
},
|
||||
|
||||
mixins: [conversationMixin, adminMixin],
|
||||
|
||||
props: {
|
||||
inboxId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isLoadingPrevious: true,
|
||||
heightBeforeLoad: null,
|
||||
conversationPanel: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
allConversations: 'getAllConversations',
|
||||
inboxesList: 'getInboxesList',
|
||||
listLoadingStatus: 'getAllMessagesLoaded',
|
||||
getUnreadCount: 'getUnreadCount',
|
||||
fetchingInboxes: 'getInboxLoadingStatus',
|
||||
loadingChatList: 'getChatListLoadingStatus',
|
||||
}),
|
||||
// Loading indicator
|
||||
// Returns corresponding loading message
|
||||
loadingIndicatorMessage() {
|
||||
if (this.fetchingInboxes) {
|
||||
return this.$t('CONVERSATION.LOADING_INBOXES');
|
||||
}
|
||||
return this.$t('CONVERSATION.LOADING_CONVERSATIONS');
|
||||
},
|
||||
getMessages() {
|
||||
const [chat] = this.allConversations.filter(
|
||||
c => c.id === this.currentChat.id
|
||||
);
|
||||
return chat;
|
||||
},
|
||||
// Get current FB Page ID
|
||||
getPageId() {
|
||||
let stateInbox;
|
||||
if (this.inboxId) {
|
||||
const inboxId = Number(this.inboxId);
|
||||
[stateInbox] = this.inboxesList.filter(
|
||||
inbox => inbox.channel_id === inboxId
|
||||
);
|
||||
} else {
|
||||
[stateInbox] = this.inboxesList;
|
||||
}
|
||||
return !stateInbox ? 0 : stateInbox.pageId;
|
||||
},
|
||||
// Get current FB Page ID link
|
||||
linkToMessage() {
|
||||
return `https://m.me/${this.getPageId}`;
|
||||
},
|
||||
getReadMessages() {
|
||||
const chat = this.getMessages;
|
||||
return chat === undefined ? null : this.readMessages(chat);
|
||||
},
|
||||
getUnReadMessages() {
|
||||
const chat = this.getMessages;
|
||||
return chat === undefined ? null : this.unReadMessages(chat);
|
||||
},
|
||||
shouldShowSpinner() {
|
||||
return (
|
||||
this.getMessages.dataFetched === undefined ||
|
||||
(!this.listLoadingStatus && this.isLoadingPrevious)
|
||||
);
|
||||
},
|
||||
|
||||
newInboxURL() {
|
||||
return frontendURL('settings/inboxes/new');
|
||||
},
|
||||
|
||||
shouldLoadMoreChats() {
|
||||
return !this.listLoadingStatus && !this.isLoadingPrevious;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
bus.$on('scrollToMessage', () => {
|
||||
this.focusLastMessage();
|
||||
this.makeMessagesRead();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
focusLastMessage() {
|
||||
setTimeout(() => {
|
||||
this.attachListner();
|
||||
}, 0);
|
||||
},
|
||||
|
||||
attachListner() {
|
||||
this.conversationPanel = this.$el.querySelector('.conversation-panel');
|
||||
this.heightBeforeLoad =
|
||||
this.getUnreadCount === 0
|
||||
? this.conversationPanel.scrollHeight
|
||||
: this.$el.querySelector('.conversation-panel .unread--toast')
|
||||
.offsetTop - 56;
|
||||
this.conversationPanel.scrollTop = this.heightBeforeLoad;
|
||||
this.conversationPanel.addEventListener('scroll', this.handleScroll);
|
||||
this.isLoadingPrevious = false;
|
||||
},
|
||||
|
||||
handleScroll(e) {
|
||||
const dataFetchCheck =
|
||||
this.getMessages.dataFetched === true && this.shouldLoadMoreChats;
|
||||
if (
|
||||
e.target.scrollTop < 100 &&
|
||||
!this.isLoadingPrevious &&
|
||||
dataFetchCheck
|
||||
) {
|
||||
this.isLoadingPrevious = true;
|
||||
this.$store
|
||||
.dispatch('fetchPreviousMessages', {
|
||||
id: this.currentChat.id,
|
||||
before: this.getMessages.messages[0].id,
|
||||
})
|
||||
.then(() => {
|
||||
this.conversationPanel.scrollTop =
|
||||
this.conversationPanel.scrollHeight -
|
||||
(this.heightBeforeLoad - this.conversationPanel.scrollTop);
|
||||
this.isLoadingPrevious = false;
|
||||
this.heightBeforeLoad =
|
||||
this.getUnreadCount === 0
|
||||
? this.conversationPanel.scrollHeight
|
||||
: this.$el.querySelector('.conversation-panel .unread--toast')
|
||||
.offsetTop - 56;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
makeMessagesRead() {
|
||||
if (this.getUnreadCount !== 0 && this.getMessages !== undefined) {
|
||||
this.$store.dispatch('markMessagesRead', {
|
||||
id: this.currentChat.id,
|
||||
lastSeen: this.getMessages.messages.last().created_at,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<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--details columns">
|
||||
<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="unread">{{ getUnreadCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
/* eslint no-extra-boolean-cast: 0 */
|
||||
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 {
|
||||
components: {
|
||||
Thumbnail,
|
||||
},
|
||||
|
||||
mixins: [timeMixin, conversationMixin],
|
||||
props: {
|
||||
chat: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
inboxesList: 'getInboxesList',
|
||||
activeInbox: 'getSelectedInbox',
|
||||
}),
|
||||
|
||||
isActiveChat() {
|
||||
return this.currentChat.id === this.chat.id;
|
||||
},
|
||||
|
||||
getUnreadCount() {
|
||||
return this.unreadMessagesCount(this.chat);
|
||||
},
|
||||
|
||||
hasUnread() {
|
||||
return this.getUnreadCount > 0;
|
||||
},
|
||||
|
||||
isInboxNameVisible() {
|
||||
return !this.activeInbox;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
cardClick(chat) {
|
||||
router.push({
|
||||
path: frontendURL(`conversations/${chat.id}`),
|
||||
});
|
||||
},
|
||||
extractMessageText(chatItem) {
|
||||
if (chatItem.content) {
|
||||
return chatItem.content;
|
||||
}
|
||||
let fileType = '';
|
||||
if (chatItem.attachment) {
|
||||
fileType = chatItem.attachment.file_type;
|
||||
} else {
|
||||
return ' ';
|
||||
}
|
||||
const key = `CHAT_LIST.ATTACHMENTS.${fileType}`;
|
||||
return `
|
||||
<i class="${this.$t(`${key}.ICON`)}"></i>
|
||||
${this.$t(`${key}.CONTENT`)}
|
||||
`;
|
||||
},
|
||||
getEmojiSVG,
|
||||
inboxName(inboxId) {
|
||||
const [stateInbox] = this.inboxesList.filter(
|
||||
inbox => inbox.channel_id === inboxId
|
||||
);
|
||||
return !stateInbox ? '' : stateInbox.label;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="conv-header">
|
||||
<div class="user">
|
||||
<Thumbnail
|
||||
:src="chat.meta.sender.thumbnail"
|
||||
size="40px"
|
||||
:badge="chat.meta.sender.channel"
|
||||
/>
|
||||
<h3 class="user--name">{{chat.meta.sender.name}}</h3>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="multiselect-box ion-headphone">
|
||||
<multiselect
|
||||
v-model="currentChat.meta.assignee"
|
||||
:options="agentList"
|
||||
label="name"
|
||||
@select="assignAgent"
|
||||
:allow-empty="true"
|
||||
deselect-label="Remove"
|
||||
placeholder="Select Agent"
|
||||
selected-label=''
|
||||
select-label="Assign"
|
||||
track-by="id"
|
||||
@remove="removeAgent"
|
||||
/>
|
||||
</div>
|
||||
<ResolveButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
||||
/* eslint no-console: 0 */
|
||||
/* eslint no-param-reassign: 0 */
|
||||
/* eslint no-shadow: 0 */
|
||||
/* global bus */
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from '../Thumbnail';
|
||||
import ResolveButton from '../../buttons/ResolveButton';
|
||||
import EmojiInput from '../emoji/EmojiInput';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'chat',
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentChatAssignee: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
agents: 'getVerifiedAgents',
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
agentList() {
|
||||
return [
|
||||
{
|
||||
confirmed: true,
|
||||
name: 'None',
|
||||
id: 0,
|
||||
role: 'agent',
|
||||
account_id: 0,
|
||||
email: 'None',
|
||||
},
|
||||
...this.agents,
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
assignAgent(agent) {
|
||||
this.$store.dispatch('assignAgent', [this.currentChat.id, agent.id]).then((response) => {
|
||||
console.log('assignAgent', response);
|
||||
bus.$emit('newToastMessage', this.$t('CONVERSATION.CHANGE_AGENT'));
|
||||
});
|
||||
},
|
||||
|
||||
removeAgent(agent) {
|
||||
console.log(agent.email);
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
Thumbnail,
|
||||
ResolveButton,
|
||||
EmojiInput,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="reply-box">
|
||||
<div class="reply-box__top" :class="{ 'is-private': private }">
|
||||
<canned-response
|
||||
v-on-clickaway="hideCannedResponse"
|
||||
data-dropdown-menu
|
||||
:on-keyenter="replaceText"
|
||||
:on-click="replaceText"
|
||||
v-if="showCannedModal"
|
||||
/>
|
||||
<emoji-input v-on-clickaway="hideEmojiPicker" :on-click="emojiOnClick" v-if="showEmojiPicker"/>
|
||||
<textarea
|
||||
rows="1"
|
||||
v-model="message"
|
||||
class="input"
|
||||
type="text"
|
||||
@click="onClick()"
|
||||
@blur="onBlur()"
|
||||
v-bind:placeholder="$t(messagePlaceHolder())"
|
||||
ref="messageInput"
|
||||
/>
|
||||
<i class="icon ion-happy-outline" :class="{ active: showEmojiPicker}" @click="toggleEmojiPicker()"></i>
|
||||
</div>
|
||||
|
||||
<div class="reply-box__bottom" >
|
||||
<ul class="tabs">
|
||||
<li class="tabs-title" v-bind:class="{ 'is-active': !private }">
|
||||
<a href="#" @click="makeReply" >Reply</a>
|
||||
</li>
|
||||
<li class="tabs-title is-private" v-bind:class="{ 'is-active': private }">
|
||||
<a href="#" @click="makePrivate">Private Note</a>
|
||||
</li>
|
||||
<li class="tabs-title message-length" v-if="message.length">
|
||||
<a :class="{ 'message-error': message.length > 620 }">{{ message.length }} / 640</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
@click="sendMessage"
|
||||
type="button"
|
||||
class="button send-button"
|
||||
:disabled="disableButton()"
|
||||
v-bind:class="{ 'disabled': message.length === 0 || message.length > 640,
|
||||
'warning': private }"
|
||||
>
|
||||
{{ private ? $t('CONVERSATION.REPLYBOX.CREATE') : $t('CONVERSATION.REPLYBOX.SEND') }}
|
||||
<i class="icon" :class="{ 'ion-android-send': !private, 'ion-android-lock': private }"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import emojione from 'emojione';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import EmojiInput from '../emoji/EmojiInput';
|
||||
import CannedResponse from './CannedResponse';
|
||||
|
||||
export default {
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
return {
|
||||
message: '',
|
||||
private: false,
|
||||
showEmojiPicker: false,
|
||||
showCannedModal: false,
|
||||
};
|
||||
},
|
||||
computed: mapGetters({
|
||||
currentChat: 'getSelectedChat',
|
||||
}),
|
||||
components: {
|
||||
EmojiInput,
|
||||
CannedResponse,
|
||||
},
|
||||
mounted() {
|
||||
/* eslint-disable no-confusing-arrow */
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (this.isEscape(e)) {
|
||||
this.hideEmojiPicker();
|
||||
this.hideCannedResponse();
|
||||
}
|
||||
if (this.isEnter(e)) {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
message(val) {
|
||||
if (this.private) {
|
||||
return;
|
||||
}
|
||||
const isSlashCommand = val[0] === '/';
|
||||
const hasNextWord = val.indexOf(' ') > -1;
|
||||
const isShortCodeActive = isSlashCommand && !hasNextWord;
|
||||
if (isShortCodeActive) {
|
||||
this.showCannedModal = true;
|
||||
if (val.length > 1) {
|
||||
const searchKey = val.substr(1, val.length);
|
||||
this.$store.dispatch('searchCannedResponse', {
|
||||
searchKey,
|
||||
});
|
||||
} else {
|
||||
this.$store.dispatch('fetchCannedResponse');
|
||||
}
|
||||
} else {
|
||||
this.showCannedModal = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isEnter(e) {
|
||||
return e.keyCode === 13;
|
||||
},
|
||||
isEscape(e) {
|
||||
return e.keyCode === 27; // ESCAPE
|
||||
},
|
||||
sendMessage() {
|
||||
const messageHasOnlyNewLines = !this.message.replace(/\n/g, '').length;
|
||||
if (messageHasOnlyNewLines) {
|
||||
return;
|
||||
}
|
||||
const messageAction = this.private ? 'addPrivateNote' : 'sendMessage';
|
||||
if (this.message.length !== 0 && !this.showCannedModal) {
|
||||
this.$store.dispatch(messageAction, [this.currentChat.id, this.message]).then(() => {
|
||||
this.$emit('scrollToMessage');
|
||||
});
|
||||
this.clearMessage();
|
||||
this.hideEmojiPicker();
|
||||
}
|
||||
},
|
||||
replaceText(message) {
|
||||
setTimeout(() => {
|
||||
this.message = message;
|
||||
}, 200);
|
||||
},
|
||||
makePrivate() {
|
||||
this.private = true;
|
||||
this.$refs.messageInput.focus();
|
||||
},
|
||||
makeReply() {
|
||||
this.private = false;
|
||||
this.$refs.messageInput.focus();
|
||||
},
|
||||
emojiOnClick(emoji) {
|
||||
this.message = emojione.shortnameToUnicode(`${this.message}${emoji.shortname} `);
|
||||
},
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
},
|
||||
toggleEmojiPicker() {
|
||||
this.showEmojiPicker = !this.showEmojiPicker;
|
||||
},
|
||||
hideEmojiPicker() {
|
||||
if (this.showEmojiPicker) {
|
||||
this.toggleEmojiPicker();
|
||||
}
|
||||
},
|
||||
hideCannedResponse() {
|
||||
this.showCannedModal = false;
|
||||
},
|
||||
|
||||
onBlur() {
|
||||
this.toggleTyping('off');
|
||||
},
|
||||
onClick() {
|
||||
this.markSeen();
|
||||
this.toggleTyping('on');
|
||||
},
|
||||
markSeen() {
|
||||
this.$store.dispatch('markSeen', {
|
||||
inboxId: this.currentChat.inbox_id,
|
||||
senderId: this.currentChat.meta.sender.id,
|
||||
});
|
||||
},
|
||||
|
||||
toggleTyping(flag) {
|
||||
this.$store.dispatch('toggleTyping', {
|
||||
flag,
|
||||
inboxId: this.currentChat.inbox_id,
|
||||
senderId: this.currentChat.meta.sender.id,
|
||||
});
|
||||
},
|
||||
disableButton() {
|
||||
const messageHasOnlyNewLines = !this.message.replace(/\n/g, '').length;
|
||||
return this.message.length === 0 || this.message.length > 640 || messageHasOnlyNewLines;
|
||||
},
|
||||
|
||||
messagePlaceHolder() {
|
||||
const placeHolder = this.private ? 'CONVERSATION.FOOTER.PRIVATE_MSG_INPUT' : 'CONVERSATION.FOOTER.MSG_INPUT';
|
||||
return placeHolder;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.send-button {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="audio message-text__wrap">
|
||||
<a-player
|
||||
:music="playerOptions"
|
||||
mode="order"
|
||||
/>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import APlayer from 'vue-aplayer';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
APlayer,
|
||||
},
|
||||
props: [
|
||||
'url',
|
||||
'readableTime',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
musicObj: {
|
||||
title: ' ',
|
||||
author: ' ',
|
||||
autoplay: false,
|
||||
narrow: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
playerOptions() {
|
||||
return {
|
||||
...this.musicObj,
|
||||
url: this.url,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="image message-text__wrap">
|
||||
<img
|
||||
:src="url"
|
||||
v-on:click="onClick"
|
||||
/>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
<woot-modal :show.sync="show" :on-close="onClose">
|
||||
<img
|
||||
:src="url"
|
||||
class="modal-image"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'url',
|
||||
'readableTime',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.show = false;
|
||||
},
|
||||
onClick() {
|
||||
this.show = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="map message-text__wrap">
|
||||
<img
|
||||
:src="locUrl"
|
||||
/>
|
||||
<span class="locname">{{label || ' '}}</span>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'lat',
|
||||
'lng',
|
||||
'label',
|
||||
'readableTime',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
accessToken: 'pk.eyJ1IjoiY2hhdHdvb3QiLCJhIjoiY2oyazVsM3d0MDBmYjJxbmkyYXlwY3hzZyJ9.uWUdfItb0sSZQ4nfwlmuPg',
|
||||
zoomLevel: 14,
|
||||
mapType: 'mapbox.streets',
|
||||
apiEndPoint: 'https://api.mapbox.com/v4/',
|
||||
h: 100,
|
||||
w: 150,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
locUrl() {
|
||||
const { apiEndPoint, mapType, lat, lng, zoomLevel, h, w, accessToken } = this;
|
||||
return `${apiEndPoint}${mapType}/${lng},${lat},${zoomLevel}/${w}x${h}.png?access_token=${accessToken}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<span class="message-text__wrap">
|
||||
<span v-html="message" class="message-text"></span>
|
||||
<span class="time">{{readableTime}}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'message',
|
||||
'readableTime',
|
||||
],
|
||||
};
|
||||
</script>
|
||||