MT#52919 CSC: QuasarMigration - Migrate Quasar from v1 to v2

Change-Id: I56c622c5074237c1c6837a892ba79174c7340f0f
(cherry picked from commit c833595bd8)
mr11.5
Hugo Zigha 2 years ago committed by Marco Capetta
parent ef3e4e5edd
commit 181adce1d3

@ -5,7 +5,7 @@ module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
parser: '@babel/eslint-parser',
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module' // Allows for the use of imports
},
@ -22,7 +22,7 @@ module.exports = {
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/essential', // Priority A: Essential (Error Prevention)
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
'plugin:vue/strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
'plugin:vue/recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
'standard'

@ -27,9 +27,14 @@
"i18n:extract-report": "sh ./bin/vue-i18n-extract/extract.sh report"
},
"dependencies": {
"@quasar/extras": "^1.12.2",
"@quasar/extras": "^1.14.0",
"@vuelidate/core": "2.0.2",
"@vuelidate/validators": "2.0.2",
"async": "^3.2.2",
"axios": "0.21.4",
"core-js": "^3.6.5",
"crypto": "npm:crypto-browserify",
"crypto-browserify": "^3.12.0",
"decode-uri-component": "^0.4.0",
"eventsource": "^2.0.2",
"file-saver": "^2.0.2",
@ -42,39 +47,48 @@
"moment": "^2.29.4",
"npm": "^9.0.0",
"qrcode": "1.5.0",
"quasar": "^1.16.3",
"quasar": "2",
"stream": "npm:stream-browserify",
"stream-browserify": "^3.0.0",
"terser": "^5.14.2",
"vue-i18n": "^8.0.0",
"vue-password-strength-meter": "^1.7.2",
"tiny-emitter": "2.1.0",
"vue": "3",
"vue-i18n": "9.2.2",
"vue-resource": "^1.5.1",
"vue-router": "4",
"vue-scrollto": "^2.18.2",
"vuelidate": "^0.7.5",
"zxcvbn": "^4.4.2"
"vuex": "4",
"zxcvbn": "^4.4.2",
"vue-simple-password-meter": "1.3.4"
},
"devDependencies": {
"@quasar/app": "^2.2.11",
"@quasar/cli": "1.2.2",
"@quasar/quasar-app-extension-testing": "^1.0.5",
"@quasar/quasar-app-extension-testing-unit-jest": "^1.0.1",
"babel-eslint": "^10.0.1",
"@babel/eslint-parser": "^7.0.0",
"@quasar/app-webpack": "3",
"@quasar/cli": "1.3.2",
"@quasar/quasar-app-extension-testing": "1.0.5",
"@quasar/quasar-app-extension-testing-unit-jest": "2.2.5",
"commander": "6.2.1",
"decomment": "0.9.4",
"dot-object": "2.1.4",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.14.0",
"eslint": "^8.10.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-quasar": "^1.0.0",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"eslint-plugin-vue": "^9.0.0",
"eslint-webpack-plugin": "^3.1.1",
"generate-password": "^1.5.1",
"glob": "7.1.6",
"is-valid-glob": "1.0.0",
"js-yaml": "3.14.1",
"parseuri": "^0.0.6",
"stylus": "0.59.0",
"stylus-loader": "7.1.3",
"uuid": "8.3.1",
"vue-wait": "1.4.8"
"vue-wait": "^1.5.3",
"vuelidate": "^0.7.7"
},
"browserslist": [
"last 10 Chrome versions",

@ -6,6 +6,10 @@
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
const ESLintPlugin = require('eslint-webpack-plugin')
const webpack = require('webpack')
module.exports = function (ctx) {
let devServerConfig = {}
try {
@ -27,10 +31,10 @@ module.exports = function (ctx) {
// https://quasar.dev/quasar-cli/boot-files
boot: [
'appConfig',
'i18n',
'api',
'filters',
'vuelidate',
'i18n',
'vue-resource',
'ngcp-call',
'user',
'routes',
@ -38,13 +42,14 @@ module.exports = function (ctx) {
'constants',
'vue-wait',
'e2e-testing',
'branding'
'branding',
'event-bus'
],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: [
'app.fonts.styl',
'app.styl'
'app.fonts.sass',
'app.sass'
],
// https://github.com/quasarframework/quasar/tree/dev/extras
@ -61,11 +66,44 @@ module.exports = function (ctx) {
'material-icons' // optional, you are not bound to it
],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
iconSet: 'material-icons', // Quasar icon set
lang: 'en-US', // Quasar language pack
config: {},
// Possible values for "importStrategy":
// * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives
// * 'all' - Manually specify what to import
importStrategy: 'auto',
// For special cases outside of where "auto" importStrategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: [
'Loading',
'Notify',
'Dialog',
'SessionStorage',
'LocalStorage',
'Dark',
'Meta'
]
},
// https://quasar.dev/quasar-cli/supporting-ts
supportTS: false,
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: {
env: {
...process.env
},
vueRouterMode: 'hash', // available values: 'hash', 'history'
// transpile: false,
@ -86,12 +124,19 @@ module.exports = function (ctx) {
// https://quasar.dev/quasar-cli/handling-webpack
extendWebpack (cfg) {
cfg.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /node_modules/
})
cfg.plugins.push(
new ESLintPlugin({ extensions: ['js', 'vue'] })
)
cfg.plugins.push(
new webpack.ProvidePlugin({
process: 'process/browser'
})
)
cfg.plugins.push(
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env)
})
)
}
},
@ -100,52 +145,32 @@ module.exports = function (ctx) {
https: false,
port: 8080,
open: true, // opens browser window automatically,
public: devServerConfig.public,
publicPath: devServerConfig.publicPath,
...(!devServerConfig.proxyAPI2localhost ? {} : {
https: true,
publicPath: devServerConfig.publicPath || '/v2/',
proxy: {
[`!${devServerConfig.publicPath || '/v2/'}`]: {
target: devServerConfig.proxyAPIFromURL,
secure: false
devMiddleware: {
publicPath: devServerConfig.publicPath,
...(!devServerConfig.proxyAPI2localhost
? {}
: {
publicPath: devServerConfig.publicPath || '/v2/'
}
)
},
...(!devServerConfig.proxyAPI2localhost
? {}
: {
https: true,
proxy: {
[`!${devServerConfig.publicPath || '/v2/'}`]: {
target: devServerConfig.proxyAPIFromURL,
secure: false
}
},
onBeforeSetupMiddleware: (devServer) => {
devServer.app.get('/', function (req, res) {
res.redirect(301, devServerConfig.publicPath || '/v2/')
})
}
},
before: function (app, server, compiler) {
app.get('/', function (req, res) {
res.redirect(301, devServerConfig.publicPath || '/v2/')
})
}
})
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
iconSet: 'material-icons', // Quasar icon set
lang: 'en-us', // Quasar language pack
config: {},
// Possible values for "importStrategy":
// * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives
// * 'all' - Manually specify what to import
importStrategy: 'auto',
// For special cases outside of where "auto" importStrategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: [
'Notify',
'Dialog',
'SessionStorage',
'LocalStorage',
'Dark',
'Meta'
]
)
},
// animations: 'all', // --- includes all animations

@ -1,7 +1,5 @@
<template>
<div id="q-app">
<router-view />
</div>
<router-view />
</template>
<script>
import { APP_NAME } from 'src/constants'
@ -25,8 +23,10 @@ export default {
this.updateTitle(route)
}
},
mounted () {
async mounted () {
this.$initWait()
this.updateTitle(this.$route)
await this.$router.push({ name: 'dashboard' })
},
methods: {
updateTitle: function (route) {

@ -1,6 +1,5 @@
import _ from 'lodash'
import Vue from 'vue'
import {
enableBlockIn,
@ -18,6 +17,8 @@ import {
disablePrivacy
} from './subscriber'
import { httpApi } from './common'
export function enableIncomingCallBlocking (id) {
return enableBlockIn(id)
}
@ -152,12 +153,12 @@ export function removeNumberFromList (id, field, value) {
Promise.resolve().then(() => {
return getPreferences(id)
}).then((result) => {
var prefs = _.cloneDeep(result)
const prefs = _.cloneDeep(result)
delete prefs._links
prefs[field] = _.get(prefs, field, []).filter((number) => {
return number !== value
})
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -209,7 +210,7 @@ export function toggleNumberInBothLists (options) {
return number !== options.number
})
}
return Vue.http.put('api/subscriberpreferences/' + options.id, prefs)
return httpApi.put('api/subscriberpreferences/' + options.id, prefs)
}).then(() => {
resolve()
}).catch((err) => {

@ -1,13 +1,19 @@
import _ from 'lodash'
import Vue from 'vue'
import axios from 'axios'
import {
getJsonBody
} from './utils'
import {
getJwt,
hasJwt
} from 'src/auth'
import { getCurrentLangAsV1Format } from 'src/i18n'
export const LIST_DEFAULT_PAGE = 1
export const LIST_DEFAULT_ROWS = 24
export const LIST_ALL_ROWS = 1000
export const API_REQUEST_DEFAULT_TIMEOUT = 30000
export const ContentType = {
json: 'application/json',
@ -19,6 +25,10 @@ export const Prefer = {
representation: 'return=representation'
}
export const httpApi = axios.create({
timeout: API_REQUEST_DEFAULT_TIMEOUT
})
const PATCH_HEADERS = {
'Content-Type': ContentType.jsonPatch,
Prefer: Prefer.minimal
@ -49,6 +59,53 @@ export class ApiResponseError extends Error {
}
}
export function initAPI ({ baseURL }) {
httpApi.defaults.baseURL = baseURL
httpApi.interceptors.request.use(function normaliseApiRequestBody (config) {
if (config) {
if (hasJwt()) {
if (config.headers) {
config.headers = {
...config.headers,
Authorization: 'Bearer ' + getJwt()
}
} else {
config = {
...config,
headers: {
Authorization: 'Bearer ' + getJwt()
}
}
}
}
if (config.method === 'POST' && (config.data === undefined || config.data === null)) {
config.data = {}
}
if (config.params) {
config.params = {
...config.params,
lang: getCurrentLangAsV1Format()
}
} else {
config.params = {
lang: getCurrentLangAsV1Format()
}
}
return config
}
})
}
export function apiCreateCancelObject () {
const CancelToken = axios.CancelToken
return CancelToken.source()
}
export function apiIsCanceledRequest (exception) {
return axios.isCancel(exception)
}
export async function getList (options) {
options = options || {}
options = _.merge({
@ -66,25 +123,26 @@ export async function getList (options) {
options.path = 'api/' + options.resource + '/'
options.root = '_embedded.ngcp:' + options.resource
}
const firstRes = await Vue.http.get(options.path, {
params: options.params,
headers: options.headers
const firstRes = await httpApi.get(options.path, {
headers: options.headers,
params: options.params
})
let secondRes = null
const firstResBody = getJsonBody(firstRes.body)
const firstResBody = getJsonBody(firstRes.data)
if (options.all === true && firstResBody.total_count > LIST_ALL_ROWS) {
secondRes = await Vue.http.get(options.path, {
params: _.merge(options.params, {
rows: firstResBody.total_count
}),
headers: options.headers
const newParams = _.merge(options.params, {
rows: firstResBody.total_count
})
secondRes = await httpApi.get(options.path, {
headers: options.headers,
params: newParams
})
}
let res = firstRes
let body = firstResBody
if (secondRes !== null) {
res = secondRes
body = getJsonBody(res.body)
body = getJsonBody(res.data)
}
const totalCount = _.get(body, 'total_count', 0)
let lastPage = Math.ceil(totalCount / options.params.rows)
@ -109,10 +167,10 @@ export async function getList (options) {
}
function handleResponseError (err) {
const code = _.get(err, 'body.code', null)
const message = _.get(err, 'body.message', null)
const code = _.get(err, 'response.data.code', null)
const message = _.get(err, 'response.data.message', null)
if (code !== null && message !== null) {
throw new ApiResponseError(err.body.code, err.body.message)
throw new ApiResponseError(err.response.data.code, err.response.data.message)
} else {
throw err
}
@ -123,9 +181,14 @@ export async function get (options) {
options = _.merge({
headers: GET_HEADERS
}, options)
const requestOptions = {
headers: options.headers,
params: options.params
let requestOptions = {
headers: options.headers
}
if (options.params) {
requestOptions = {
...requestOptions,
params: options.params
}
}
if (options.blob === true) {
requestOptions.responseType = 'blob'
@ -135,12 +198,12 @@ export async function get (options) {
path = 'api/' + options.resource + '/' + options.resourceId
}
try {
const res = await Vue.http.get(path, requestOptions)
const res = await httpApi.get(path, requestOptions)
let body = null
if (options.blob === true) {
body = URL.createObjectURL(res.body)
body = URL.createObjectURL(res.data)
} else {
body = normalizeEntity(getJsonBody(res.body))
body = normalizeEntity(getJsonBody(res.data))
}
return body
} catch (err) {
@ -165,7 +228,7 @@ export async function patch (operation, options) {
path = 'api/' + options.resource + '/' + options.resourceId
}
try {
return await Vue.http.patch(path, [body], {
return await httpApi.patch(path, [body], {
headers: options.headers
})
} catch (err) {
@ -193,7 +256,7 @@ export async function patchFull (operation, options) {
}
})
const res = await patch(operation, options)
return normalizeEntity(getJsonBody(res.body))
return normalizeEntity(getJsonBody(res.data))
}
export function patchReplaceFull (options) {
@ -218,14 +281,14 @@ export async function post (options) {
path = 'api/' + options.resource + '/'
}
try {
const res = await Vue.http.post(path, options.body, {
const res = await httpApi.post(path, options.body, {
headers: options.headers
})
const hasBody = res.body !== undefined && res.body !== null && res.body !== ''
const hasBody = res.data !== undefined && res.data !== null && res.data !== ''
if (hasBody) {
return normalizeEntity(getJsonBody(res.body))
} else if (!hasBody && res.headers.has('Location')) {
return _.last(res.headers.get('Location').split('/'))
return normalizeEntity(getJsonBody(res.data))
} else if (!hasBody && res?.headers?.location) {
return _.last(res.headers.location.split('/'))
} else {
return null
}
@ -254,11 +317,11 @@ export async function put (options) {
path = 'api/' + options.resource + '/' + options.resourceId
}
try {
const res = await Vue.http.put(path, options.body, {
const res = await httpApi.put(path, options.body, {
headers: options.headers
})
if (options.headers.Prefer === Prefer.representation) {
return normalizeEntity(getJsonBody(res.body))
return normalizeEntity(getJsonBody(res.data))
} else {
return null
}
@ -291,7 +354,7 @@ export async function del (options) {
path = 'api/' + options.resource + '/' + options.resourceId
}
try {
await Vue.http.delete(path, requestOptions)
await httpApi.delete(path, requestOptions)
} catch (err) {
handleResponseError(err)
}
@ -303,10 +366,10 @@ export function getFieldList (options) {
options = _.merge({
headers: GET_HEADERS
}, options)
Vue.http.get(options.path, {
httpApi.get(options.path, {
headers: options.headers
}).then((result) => {
const fieldList = getJsonBody(result.body)[options.field]
const fieldList = getJsonBody(result.data)[options.field]
resolve(fieldList)
}).catch((err) => {
reject(err)

@ -3,13 +3,12 @@ import _ from 'lodash'
import {
saveAs
} from 'file-saver'
import Vue from 'vue'
import {
getIncomingCallBlocking,
getOutgoingCallBlocking
} from './call-blocking'
import {
getList, LIST_DEFAULT_ROWS
getList, LIST_DEFAULT_ROWS, httpApi
} from './common'
export function getConversations (options) {
@ -54,9 +53,9 @@ export function getConversations (options) {
export function downloadVoiceMail (id) {
return new Promise((resolve, reject) => {
Vue.http.get('api/voicemailrecordings/' + id, { responseType: 'blob' })
httpApi.get('api/voicemailrecordings/' + id, { responseType: 'blob' })
.then((res) => {
return res.blob()
return res.data
}).then(voicemail => {
saveAs((voicemail), 'voicemail-' + id + '.wav')
resolve()
@ -68,9 +67,9 @@ export function downloadVoiceMail (id) {
export function downloadFax (id) {
return new Promise((resolve, reject) => {
Vue.http.get('api/faxrecordings/' + id, { responseType: 'blob' })
httpApi.get('api/faxrecordings/' + id, { responseType: 'blob' })
.then((res) => {
return res.blob()
return res.data
}).then(fax => {
saveAs((fax), 'fax-' + id + '.tif')
resolve()
@ -83,9 +82,9 @@ export function downloadFax (id) {
export function playVoiceMail (options) {
return new Promise((resolve, reject) => {
const params = { format: options.format }
Vue.http.get(`api/voicemailrecordings/${options.id}`, { params: params, responseType: 'blob' })
httpApi.get(`api/voicemailrecordings/${options.id}`, { params: params, responseType: 'blob' })
.then((res) => {
resolve(URL.createObjectURL(res.body))
resolve(URL.createObjectURL(res.data))
}).catch((err) => {
reject(err)
})
@ -113,7 +112,7 @@ export function getOutgoingBlocked (id) {
}
export async function deleteVoicemail (id) {
const res = await Vue.http.delete('api/voicemails/' + id)
const res = await httpApi.delete('api/voicemails/' + id)
return res.status >= 200
}
@ -125,6 +124,6 @@ export async function getAllCallsOrVoicemails (options) {
}
export async function deleteFax (id) {
const res = await Vue.http.delete('api/faxes/' + id)
const res = await httpApi.delete('api/faxes/' + id)
return res.status >= 200
}

@ -23,7 +23,7 @@ export async function setFaxServerField (options) {
// searching for duplicates
const destinationsIds = options.value.map(d => d.destination)
if ((new Set(destinationsIds)).size !== destinationsIds.length) {
throw Error(i18n.t('The Destination Email is already used'))
throw Error(i18n.global.tc('The Destination Email is already used'))
}
}
return patchReplaceFull({
@ -50,7 +50,7 @@ export async function setMailToFaxSettingField (options) {
// searching for duplicates
const destinationsIds = options.value.map(d => d.destination)
if ((new Set(destinationsIds)).size !== destinationsIds.length) {
throw Error(i18n.t('The Notify Email is already used'))
throw Error(i18n.global.tc('The Notify Email is already used'))
}
}
return patchReplaceFull({

@ -1,12 +1,11 @@
import Vue from 'vue'
import { patchReplaceFull } from 'src/api/common'
import { patchReplaceFull, httpApi } from 'src/api/common'
export async function getAutoAttendants (options) {
const params = { ...options, ...{ expand: 'all' } }
const res = await Vue.http.get('api/autoattendants/', {
const res = await httpApi.get('api/autoattendants/', {
params: params
})
return res.body.total_count > 0 ? res.body : []
return res.data.total_count > 0 ? res.data : []
}
export async function editSubscriberSlots (options) {

@ -1,6 +1,5 @@
import _ from 'lodash'
import Vue from 'vue'
import {
getSubscribers
} from './subscriber'
@ -11,7 +10,8 @@ import {
getList,
get,
patchAdd,
patchRemove
patchRemove,
httpApi
} from './common'
export const createId = v4
@ -77,7 +77,7 @@ export function getModel (id) {
export async function getModelImage (id, type) {
try {
const res = await Vue.http.get('api/pbxdevicemodelimages/' + id, {
const res = await httpApi.get('api/pbxdevicemodelimages/' + id, {
responseType: 'blob',
params: {
type: type
@ -85,8 +85,8 @@ export async function getModelImage (id, type) {
})
return {
id: id,
url: URL.createObjectURL(res.body),
blob: res.body
url: URL.createObjectURL(res.data),
blob: res.data
}
} catch (err) {
return {
@ -122,7 +122,7 @@ export function getAllSoundSets (options) {
}
export function removeSoundSet (id) {
return Vue.http.delete('api/soundsets/' + id)
return httpApi.delete('api/soundsets/' + id)
}
export function getSoundSet (id) {
@ -144,7 +144,7 @@ export function editSoundSetFields (id, fields) {
}).then((result) => {
const prefs = Object.assign(result, fields)
delete fields._links
return Vue.http.put('api/soundsets/' + id, prefs)
return httpApi.put('api/soundsets/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -155,7 +155,7 @@ export function editSoundSetFields (id, fields) {
export function createSoundSet (soundSet) {
return new Promise((resolve, reject) => {
Vue.http.post('api/soundsets/', soundSet).then(() => {
httpApi.post('api/soundsets/', soundSet).then(() => {
resolve()
}).catch((err) => {
reject(err)
@ -174,9 +174,9 @@ export function setSoundSetDescription (id, value) {
export function playSoundFile (options) {
return new Promise((resolve, reject) => {
const params = { format: options.format }
Vue.http.get(`api/soundfilerecordings/${options.id}`, { params: params, responseType: 'blob' })
httpApi.get(`api/soundfilerecordings/${options.id}`, { params: params, responseType: 'blob' })
.then((res) => {
resolve(URL.createObjectURL(res.body))
resolve(URL.createObjectURL(res.data))
}).catch((err) => {
reject(err)
})
@ -194,16 +194,12 @@ export function uploadSoundFile (options, onProgress) {
handle: options.item.handle
}
const json = JSON.stringify(fields)
const requestKey = `previous-${options.item.handle}-request`
formData.append('json', json)
if (options.file) {
formData.append('soundfile', options.file)
}
Vue.http.post('api/soundfiles/', formData, {
before (request) {
Vue[requestKey] = request
},
progress (e) {
httpApi.post('api/soundfiles/', formData, {
onUploadProgress (e) {
if (e.lengthComputable) {
onProgress(Math.ceil((e.loaded / e.total) * 100))
}

@ -8,9 +8,9 @@ import _ from 'lodash'
import {
getList,
patchReplace,
patchReplaceFull
patchReplaceFull,
httpApi
} from './common'
import Vue from 'vue'
export function getDevices (options) {
return new Promise((resolve, reject) => {
@ -67,15 +67,15 @@ export function getDeviceList (options) {
export function createDevice (deviceData) {
return new Promise((resolve, reject) => {
Vue.http.post('api/pbxdevices/', {
httpApi.post('api/pbxdevices/', {
station_name: deviceData.stationName,
identifier: deviceData.identifier,
profile_id: deviceData.profile
}).then((res) => {
resolve(res)
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -85,11 +85,11 @@ export function createDevice (deviceData) {
export function removeDevice (id) {
return new Promise((resolve, reject) => {
Vue.http.delete('api/pbxdevices/' + id).then(() => {
httpApi.delete('api/pbxdevices/' + id).then(() => {
resolve()
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}

@ -4,16 +4,21 @@ import {
patchReplaceFull,
getAsBlob,
get,
post
post,
httpApi
} from './common'
import {
PBX_CONFIG_ORDER_BY,
PBX_CONFIG_ORDER_DIRECTION
} from './pbx-config'
import Vue from 'vue'
import {
Platform
} from 'quasar'
import {
getJwt,
hasJwt
} from 'src/auth'
import { getCurrentLangAsV1Format } from 'src/i18n'
export function getSoundSets (options) {
return new Promise((resolve, reject) => {
@ -51,7 +56,7 @@ export function getSoundSetList (options) {
export function createSoundSet (soundSet) {
return new Promise((resolve, reject) => {
Vue.http.post('api/soundsets/', soundSet).then(() => {
httpApi.post('api/soundsets/', soundSet).then(() => {
resolve()
}).catch((err) => {
reject(err)
@ -61,11 +66,11 @@ export function createSoundSet (soundSet) {
export function removeSoundSet (soundSetId) {
return new Promise((resolve, reject) => {
Vue.http.delete('api/soundsets/' + soundSetId).then(() => {
httpApi.delete('api/soundsets/' + soundSetId).then(() => {
resolve()
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -193,27 +198,59 @@ export function uploadSoundFile (options) {
filename: options.soundFileData.name
}))
formData.append('soundfile', options.soundFileData)
Vue.http.post('api/soundfiles/', formData, {
before (request) {
options.initialized(request)
},
progress (progressEvent) {
const initializedSoundFiles = httpApi.interceptors.request.use(function (config) {
options.initialized(config)
if (hasJwt()) {
if (config.headers) {
config.headers = {
...config.headers,
Authorization: 'Bearer ' + getJwt()
}
} else {
config = {
...config,
headers: {
Authorization: 'Bearer ' + getJwt()
}
}
}
}
if (config.method === 'POST' && (config.data === undefined || config.data === null)) {
config.data = {}
}
if (config.params) {
config.params = {
...config.params,
lang: getCurrentLangAsV1Format()
}
} else {
config.params = {
lang: getCurrentLangAsV1Format()
}
}
return config
})
httpApi.post('api/soundfiles/', formData, {
onUploadProgress (progressEvent) {
if (progressEvent.lengthComputable) {
options.progressed(Math.ceil((progressEvent.loaded / progressEvent.total) * 100))
}
}
}).then((res) => {
const fileId = _.last(res.headers.get('location').split(/\//))
const fileId = _.last(res.headers.location.split(/\//))
return Promise.all([
get({ path: 'api/soundfiles/' + fileId }),
getSoundFile({ id: fileId })
])
}).then((res) => {
httpApi.interceptors.request.eject(initializedSoundFiles)
resolve({
soundFile: res[0],
soundFileUrl: res[1]
})
}).catch((err) => {
httpApi.interceptors.request.eject(initializedSoundFiles)
reject(err)
})
} else {
@ -280,11 +317,11 @@ export function setUseParent (options) {
export function removeSoundFile (soundFileId) {
return new Promise((resolve, reject) => {
Vue.http.delete('api/soundfiles/' + soundFileId).then(() => {
httpApi.delete('api/soundfiles/' + soundFileId).then(() => {
resolve()
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}

@ -1,14 +1,14 @@
import _ from 'lodash'
import Vue from 'vue'
import {
getList,
patchReplace
patchReplace,
httpApi
} from './common'
export function createReminder (subscriberId) {
return new Promise((resolve, reject) => {
Vue.http.post('api/reminders/', {
httpApi.post('api/reminders/', {
subscriber_id: subscriberId,
time: '00:00',
recur: 'never',

@ -1,8 +1,7 @@
import _ from 'lodash'
import Vue from 'vue'
import { i18n } from 'src/boot/i18n'
import { getFieldList } from './common'
import { getFieldList, httpApi } from './common'
export function getSpeedDialsById (id) {
return new Promise((resolve, reject) => {
@ -13,7 +12,7 @@ export function getSpeedDialsById (id) {
const sortedResult = _.sortBy(result, ['slot'])
resolve(sortedResult)
}).catch((err) => {
reject(err.body.message)
reject(err)
})
})
}
@ -30,13 +29,13 @@ export function getUnassignedSlots (id) {
const slotOptions = []
unassignedSlots.forEach((slot) => {
slotOptions.push({
label: `${i18n.t('Slot')} ${slot}`,
label: `${i18n.global.tc('Slot')} ${slot}`,
value: slot
})
})
resolve(slotOptions)
}).catch((err) => {
reject(err.body.message)
reject(err)
})
})
}
@ -47,14 +46,14 @@ export function unassignSpeedDialSlot (options) {
const headers = {
'Content-Type': 'application/json-patch+json'
}
Vue.http.patch('api/speeddials/' + options.id, [{
httpApi.patch('api/speeddials/' + options.id, [{
op: 'replace',
path: '/speeddials',
value: updatedAssignedSlots
}], { headers: headers }).then(() => {
resolve()
}).catch((err) => {
reject(err.body.message)
reject(err)
})
})
}
@ -64,14 +63,14 @@ export function addSlotToSpeedDials (options) {
const headers = {
'Content-Type': 'application/json-patch+json'
}
Vue.http.patch('api/speeddials/' + options.id, [{
httpApi.patch('api/speeddials/' + options.id, [{
op: 'replace',
path: '/speeddials',
value: options.slots
}], { headers: headers }).then(() => {
resolve()
}).catch((err) => {
reject(err.body.message)
reject(err)
})
})
}

@ -1,6 +1,5 @@
import _ from 'lodash'
import Vue from 'vue'
import {
getJsonBody
} from './utils'
@ -12,7 +11,8 @@ import {
patchReplace,
patchRemove,
patchReplaceFull,
patchAddFull
patchAddFull,
httpApi
} from './common'
import {
@ -21,8 +21,8 @@ import {
export function getPreferences (id) {
return new Promise((resolve, reject) => {
Vue.http.get('api/subscriberpreferences/' + id).then((result) => {
resolve(getJsonBody(result.body))
httpApi.get('api/subscriberpreferences/' + id).then((result) => {
resolve(getJsonBody(result.data))
}).catch((err) => {
reject(err)
})
@ -30,8 +30,8 @@ export function getPreferences (id) {
}
export async function getPreferencesDefs (id) {
const result = await Vue.http.get('api/subscriberpreferencedefs/')
return getJsonBody(result.body)
const result = await httpApi.get('api/subscriberpreferencedefs/')
return getJsonBody(result.data)
}
export async function setPreference (id, field, value) {
@ -93,9 +93,9 @@ export function getNcosLevels (options) {
}
export async function getNcosSet () {
let streams = []
const res = await Vue.http.get('api/v2/ncos/sets/')
if (res.body.total_count > 0) {
streams = getJsonBody(res.body)._embedded['ngcp:ncos/sets']
const res = await httpApi.get('api/v2/ncos/sets/')
if (res.data.total_count > 0) {
streams = getJsonBody(res.data)._embedded['ngcp:ncos/sets']
}
return streams
}
@ -186,7 +186,7 @@ export function prependItemToArrayPreference (id, field, value) {
delete prefs._links
prefs[field] = _.get(prefs, field, [])
prefs[field] = [value].concat(prefs[field])
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -200,11 +200,11 @@ export function appendItemToArrayPreference (id, field, value) {
Promise.resolve().then(() => {
return getPreferences(id)
}).then((result) => {
var prefs = _.cloneDeep(result)
const prefs = _.cloneDeep(result)
delete prefs._links
prefs[field] = _.get(prefs, field, [])
prefs[field].push(value)
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -222,7 +222,7 @@ export function editItemInArrayPreference (id, field, itemIndex, value) {
delete prefs._links
if (_.isArray(prefs[field]) && itemIndex < prefs[field].length) {
prefs[field][itemIndex] = value
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
} else {
return Promise.reject(new Error('Array index does not exists'))
}
@ -245,7 +245,7 @@ export function removeItemFromArrayPreference (id, field, itemIndex) {
_.remove(prefs[field], (value, index) => {
return index === itemIndex
})
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -316,15 +316,15 @@ export function disablePrivacy (id) {
export function createSubscriber (subscriber) {
return new Promise((resolve, reject) => {
Vue.http.post('api/subscribers/', subscriber, {
httpApi.post('api/subscribers/', subscriber, {
params: {
customer_id: subscriber.customer_id
}
}).then((res) => {
resolve(_.last(_.split(res.headers.get('Location'), '/')))
resolve(_.last(res.headers.location.split('/')))
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -334,11 +334,11 @@ export function createSubscriber (subscriber) {
export function deleteSubscriber (id) {
return new Promise((resolve, reject) => {
Vue.http.delete('api/subscribers/' + id).then(() => {
httpApi.delete('api/subscribers/' + id).then(() => {
resolve()
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -348,7 +348,7 @@ export function deleteSubscriber (id) {
export function setField (id, field, value) {
return new Promise((resolve, reject) => {
Vue.http.patch('api/subscribers/' + id, [{
httpApi.patch('api/subscribers/' + id, [{
op: 'replace',
path: '/' + field,
value: value
@ -360,8 +360,8 @@ export function setField (id, field, value) {
}).then((result) => {
resolve(result)
}).catch((err) => {
if (err.status >= 400) {
reject(new Error(err.body.message))
if (err.response.status >= 400) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -527,7 +527,7 @@ export function getSubscribersByCallQueueEnabled () {
}
export function addNewCallQueueConfig (id, config) {
return Vue.http.put('api/subscriberpreferences/' + id, config)
return httpApi.put('api/subscriberpreferences/' + id, config)
}
export function editCallQueuePreference (id, config) {
@ -538,7 +538,7 @@ export function editCallQueuePreference (id, config) {
}).then((result) => {
const prefs = Object.assign(result, $prefs)
delete prefs._links
return Vue.http.put('api/subscriberpreferences/' + id, prefs)
return httpApi.put('api/subscriberpreferences/' + id, prefs)
}).then(() => {
resolve()
}).catch((err) => {
@ -557,7 +557,7 @@ export function setWrapUpTime (id, wrapUpTime) {
export function removeCallQueueConfig (subscriberId) {
const param = { cloud_pbx_callqueue: false }
return Vue.http.put('api/subscriberpreferences/' + subscriberId, param)
return httpApi.put('api/subscriberpreferences/' + subscriberId, param)
}
export function getAllPreferences (options) {
@ -644,7 +644,7 @@ export async function resetPassword ({ username, domain = '' }) {
type: 'subscriber',
username
}
return await Vue.http.post('api/passwordreset/', payLoad)
return await httpApi.post('api/passwordreset/', payLoad)
}
export async function recoverPassword (data) {
@ -652,16 +652,16 @@ export async function recoverPassword (data) {
new_password: data.password,
token: data.token
}
return await Vue.http.post('api/passwordrecovery/', payLoad)
return await httpApi.post('api/passwordrecovery/', payLoad)
}
export async function getBrandingLogo (subscriberId) {
const url = 'api/resellerbrandinglogos/?subscriber_id=' + subscriberId
try {
const res = await Vue.http.get(url, {
const res = await httpApi.get(url, {
responseType: 'blob'
})
return URL.createObjectURL(res.body)
return URL.createObjectURL(res.data)
} catch (err) {
return null
}
@ -669,11 +669,11 @@ export async function getBrandingLogo (subscriberId) {
export async function getRecordings (options) {
const data = { recordings: [], total_count: 0 }
const res = await Vue.http.get('api/callrecordings/', {
const res = await httpApi.get('api/callrecordings/', {
params: options
})
if (res.body.total_count > 0) {
const recordings = getJsonBody(res.body)._embedded['ngcp:callrecordings']
if (res.data.total_count > 0) {
const recordings = getJsonBody(res.data)._embedded['ngcp:callrecordings']
data.recordings = recordings.map(recording => {
return {
id: recording.id,
@ -683,27 +683,27 @@ export async function getRecordings (options) {
files: []
}
})
data.total_count = res.body.total_count
data.total_count = res.data.total_count
}
return data
}
export async function getRecordingStreams (recId) {
let streams = []
const res = await Vue.http.get('api/callrecordingstreams/', {
const res = await httpApi.get('api/callrecordingstreams/', {
params: {
recording_id: recId
}
})
if (res.body.total_count > 0) {
streams = getJsonBody(res.body)._embedded['ngcp:callrecordingstreams']
if (res.data.total_count > 0) {
streams = getJsonBody(res.data)._embedded['ngcp:callrecordingstreams']
}
return streams
}
export async function downloadRecordingStream (fileId) {
const res = await Vue.http.get('api/callrecordingfiles/' + fileId, { responseType: 'blob' })
return res.body
const res = await httpApi.get('api/callrecordingfiles/' + fileId, { responseType: 'blob' })
return res.data
}
export async function getSubscriberRegistrations (options) {
@ -748,7 +748,7 @@ export async function createPhonebook (data) {
number: data.number,
shared: data.shared
}
return await Vue.http.post('api/phonebookentries/', payLoad)
return await httpApi.post('api/phonebookentries/', payLoad)
}
export function setValueShared (id, value) {
return setPreferencePhonebook(id, 'shared', value)

@ -1,11 +1,11 @@
import _ from 'lodash'
import Vue from 'vue'
import {
get,
post,
getList,
patchReplace
patchReplace,
httpApi
} from './common'
import { getFaxServerSettings } from 'src/api/fax'
@ -13,19 +13,19 @@ export function login (username, password) {
return new Promise((resolve, reject) => {
let jwt = null
let subscriberId = null
Vue.http.post('login_jwt', {
httpApi.post('login_jwt', {
username: username,
password: password
}).then((result) => {
jwt = result.body.jwt
subscriberId = result.body.subscriber_id + ''
jwt = result.data.jwt
subscriberId = result.data.subscriber_id + ''
resolve({
jwt: jwt,
subscriberId: subscriberId
})
}).catch((err) => {
if (err.status && err.status >= 400) {
reject(new Error(err.body.message))
if (err.response) {
reject(new Error(err.response.data.message))
} else {
reject(err)
}
@ -35,16 +35,16 @@ export function login (username, password) {
export async function loginByExchangeToken (token) {
try {
const res = await Vue.http.post('login_jwt', {
const res = await httpApi.post('login_jwt', {
token: token
})
return {
jwt: res.body?.jwt,
subscriberId: res.body?.subscriber_id + ''
jwt: res.data?.jwt,
subscriberId: res.data?.subscriber_id + ''
}
} catch (err) {
if (err.status && err.status >= 400) {
throw new Error(err.body.message)
if (err.response.status && err.response.status >= 400) {
throw new Error(err.response.data.message)
} else {
throw err
}

@ -1,10 +1,10 @@
import _ from 'lodash'
import Vue from 'vue'
import {
get,
getList,
patchReplace
patchReplace,
httpApi
} from './common'
export function getVoiceboxSettings (subscriberId) {
@ -83,7 +83,7 @@ export function getVoiceboxGreetingByType (options) {
export function deleteVoiceboxGreetingById (id) {
return new Promise((resolve, reject) => {
Vue.http.delete(`api/voicemailgreetings/${id}`).then(() => {
httpApi.delete(`api/voicemailgreetings/${id}`).then(() => {
resolve()
}).catch((err) => {
reject(err)
@ -91,14 +91,11 @@ export function deleteVoiceboxGreetingById (id) {
})
}
export function createNewGreeting (formData, onProgress, type) {
export function createNewGreeting (formData, onProgress, cancelToken) {
return new Promise((resolve, reject) => {
const requestKey = `previous${_.capitalize(type)}Request`
Vue.http.post('api/voicemailgreetings/', formData, {
before (request) {
Vue[requestKey] = request
},
progress (e) {
httpApi.post('api/voicemailgreetings/', formData, {
cancelToken,
onUploadProgress (e) {
if (e.lengthComputable) {
onProgress(Math.ceil((e.loaded / e.total) * 100))
}
@ -130,7 +127,7 @@ export function uploadGreeting (options) {
if (_.some(greetings.items, { dir: options.data.dir })) {
deleteVoiceboxGreetingById(greetings.items[0].id)
}
return createNewGreeting(formData, options.onProgress, options.data.dir)
return createNewGreeting(formData, options.onProgress, options.cancelToken)
}).then(() => {
resolve()
}).catch((err) => {
@ -139,20 +136,12 @@ export function uploadGreeting (options) {
})
}
export function abortPreviousRequest (name) {
return new Promise((resolve) => {
const requestKey = `previous${_.capitalize(name)}Request`
Vue[requestKey].abort()
resolve()
})
}
export function playGreeting (options) {
return new Promise((resolve, reject) => {
const params = { format: options.format }
Vue.http.get(`api/voicemailgreetings/${options.id}`, { params: params, responseType: 'blob' })
httpApi.get(`api/voicemailgreetings/${options.id}`, { params: params, responseType: 'blob' })
.then((res) => {
resolve(URL.createObjectURL(res.body))
resolve(URL.createObjectURL(res.data))
}).catch((err) => {
reject(err)
})

@ -0,0 +1,7 @@
import appConfig from 'src/config/app'
import { initAPI } from 'src/api/common'
export default ({ app }) => {
initAPI({ baseURL: appConfig.baseHttpUrl })
}

@ -1,6 +1,6 @@
import appConfig from '../config/app'
export default async ({ Vue, store, router, app }) => {
Vue.prototype.$appConfig = appConfig
app.$appConfig = appConfig
export default async ({ app, store }) => {
app.config.globalProperties.$appConfig = appConfig
store.$appConfig = appConfig
}

@ -1,8 +1,8 @@
import { colors } from 'quasar'
import { getCssVar } from 'quasar'
export default async ({ Vue, store, app }) => {
await store.dispatch('user/setDefaultBranding', {
primaryColor: colors.getBrand('primary'),
secondaryColor: colors.getBrand('secondary')
primaryColor: getCssVar('primary'),
secondaryColor: getCssVar('secondary')
})
}

@ -1,17 +1,17 @@
export default ({ Vue, app }) => {
Vue.prototype.$faxQualityOptions = [
export default ({ app }) => {
app.config.globalProperties.$faxQualityOptions = [
{
get label () { return app.i18n.t('Normal') },
get label () { return app.i18n.global.tc('Normal') },
value: 'normal'
},
{
get label () { return app.i18n.t('Fine') },
get label () { return app.i18n.global.tc('Fine') },
value: 'fine'
},
{
get label () { return app.i18n.t('Super') },
get label () { return app.i18n.global.tc('Super') },
value: 'super'
}
]
Vue.prototype.$faxQualityOptionsDefault = Vue.prototype.$faxQualityOptions[0]
app.config.globalProperties.$faxQualityOptionsDefault = app.config.globalProperties.$faxQualityOptions[0]
}

@ -1,13 +1,15 @@
import _ from 'lodash'
import { getCurrentInstance } from 'vue'
export default ({ app, Vue }) => {
Vue.mixin({
export default ({ app }) => {
app.mixin({
mounted () {
if (this.$vnode && this.$el && this.$el.setAttribute && this.$el.getAttribute && !this.$el.getAttribute('data-cy')) {
let dataCy = _.get(this.$vnode, 'componentOptions.Ctor.extendOptions.name', null)
if (dataCy !== null && this.$vnode.key) {
dataCy += '--' + this.$vnode.key
const vnode = getCurrentInstance()?.vnode
if (vnode && this.$el && this.$el.setAttribute && this.$el.getAttribute && !this.$el.getAttribute('data-cy')) {
let dataCy = _.get(vnode, 'type.name', null)
if (dataCy !== null && vnode?.key) {
dataCy += '--' + vnode?.key
}
if (dataCy !== null) {
this.$el.setAttribute('data-cy', _.kebabCase(dataCy))

@ -0,0 +1,10 @@
import emitter from 'tiny-emitter/instance'
export default async ({ app, store }) => {
app.config.globalProperties.emitter = {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}
}

@ -1,5 +1,4 @@
import Vue from 'vue'
import NumberFilter from 'src/filters/number'
import NumberFormatFilter, {
normalizeDestination
@ -23,22 +22,24 @@ import {
timeSetWeekdays
} from 'src/filters/time-set'
export default () => {
Vue.filter('number', NumberFilter)
Vue.filter('readableDate', DateFilter)
Vue.filter('numberFormat', NumberFormatFilter)
Vue.filter('destinationFormat', normalizeDestination)
Vue.filter('smartTime', smartTime)
Vue.filter('startCase', startCase)
Vue.filter('wholeCurrency', WholeCurrency)
Vue.filter('seatName', displayName)
Vue.filter('groupName', displayName)
Vue.filter('displayName', displayName)
Vue.filter('time', time)
Vue.filter('weekday', weekday)
Vue.filter('timeSetDateExact', timeSetDateExact)
Vue.filter('timeSetWeekdays', timeSetWeekdays)
Vue.filter('timeSetDateRange', timeSetDateRange)
Vue.filter('timeSetOfficeHoursSameTime', timeSetOfficeHoursSameTime)
Vue.filter('timeSetTimes', timeSetTimes)
export default ({ app }) => {
app.config.globalProperties.$filters = {
number: NumberFilter,
readableDate: DateFilter,
numberFormat: NumberFormatFilter,
destinationFormat: normalizeDestination,
smartTime,
startCase,
wholeCurrency: WholeCurrency,
seatName: displayName,
groupName: displayName,
displayName,
time,
weekday,
timeSetDateExact,
timeSetWeekdays,
timeSetDateRange,
timeSetOfficeHoursSameTime,
timeSetTimes
}
}

@ -1,8 +1,6 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import {
messages,
import { createI18n } from 'vue-i18n'
import messages, {
getLangFromBrowserDefaults,
setLanguage
} from 'src/i18n'
@ -10,22 +8,23 @@ import {
getSession
} from 'src/storage'
Vue.use(VueI18n)
export const defaultLocale = 'en-US'
export const i18n = new VueI18n({
const messageLoaded = messages()
const currentLocale = getSession('locale') || getLangFromBrowserDefaults() || defaultLocale
export const i18n = createI18n({
locale: defaultLocale,
fallbackLocale: defaultLocale,
formatFallbackMessages: true,
messages
messages: messageLoaded
})
export default ({ app, store }) => {
const currentLocale = getSession('locale') || getLangFromBrowserDefaults() || defaultLocale
export default async ({ app, store }) => {
app.use(i18n)
app.i18n = i18n
store.$i18n = i18n
setLanguage(currentLocale)
await setLanguage(currentLocale)
store.watch(() => i18n.locale, () => {
store.dispatch('reloadLanguageRelatedData')
})

@ -11,10 +11,11 @@ import {
callUnMute,
callUnMuteRemote
} from 'src/api/ngcp-call'
import { i18n } from 'boot/i18n'
export default async ({ Vue, app, store }) => {
export default async ({ app, store }) => {
callConfigure({
baseWebSocketUrl: app.$appConfig.baseWsUrl + '/wss/sip'
baseWebSocketUrl: app.config.globalProperties.$appConfig.baseWsUrl + '/wss/sip'
})
const callFailed = (event) => {
let cause = event.cause
@ -30,7 +31,7 @@ export default async ({ Vue, app, store }) => {
callEvent.on('disconnected', ({ error, code }) => {
let errorMessage = null
if (error) {
errorMessage = app.i18n.t('WebSocket connection to kamailio lb failed with code {code}', {
errorMessage = i18n.global.tc('WebSocket connection to kamailio lb failed with code {code}', {
code: code
})
}

@ -1,5 +1,4 @@
import routes from 'src/router/routes'
import {
Dark
} from 'quasar'
@ -21,6 +20,21 @@ export default ({ app, router, store }) => {
next()
}
} else {
/* ==== A VOIR ===== if (to.fullPath === '/user/fax-settings') {
if (store.getters['user/hasFaxCapability']) {
next()
} else {
next('/')
}
} else if (to.fullPath === '/user/customer/*') {
if (store.getters['user/isOldCSCProxyingAllowed']) {
next()
} else {
next('/')
}
} else {
next()
} */
// already authorized user
switch (to.path) {
case '/login':
@ -35,9 +49,9 @@ export default ({ app, router, store }) => {
break
default:
if (to.meta?.profileAttribute) {
app.store.getters['user/hasSubscriberProfileAttribute'](to.meta.profileAttribute) ? next() : next('/')
store.getters['user/hasSubscriberProfileAttribute'](to.meta.profileAttribute) ? next() : next('/')
} else if (to.meta?.profileAttributes) {
app.store.getters['user/hasSubscriberProfileAttributes'](to.meta.profileAttributes) ? next() : next('/')
store.getters['user/hasSubscriberProfileAttributes'](to.meta.profileAttributes) ? next() : next('/')
} else {
next()
}
@ -57,6 +71,5 @@ export default ({ app, router, store }) => {
}
store.$router = router
router.addRoutes(routes(app))
Dark.set(true)
}

@ -1,25 +0,0 @@
import VueResource from 'vue-resource'
import {
getJwt,
hasJwt
} from 'src/auth'
import { getCurrentLangAsV1Format } from 'src/i18n'
export default ({ Vue, app }) => {
Vue.use(VueResource)
Vue.http.options.root = app.$appConfig.baseHttpUrl
Vue.http.interceptors.push(function (request, next) {
if (hasJwt()) {
request.headers.set('Authorization', 'Bearer ' + getJwt())
}
if (request.method === 'POST' && (request.body === undefined || request.body === null)) {
request.body = {}
}
if (!request.params) {
request.params = {}
}
request.params.lang = getCurrentLangAsV1Format()
next()
})
}

@ -1,6 +1,6 @@
import VueScrollTo from 'vue-scrollto'
export default ({ Vue }) => {
Vue.use(VueScrollTo)
export default ({ app }) => {
app.use(VueScrollTo)
}

@ -1,9 +1,12 @@
import VueWait from 'vue-wait'
import { createVueWait } from 'vue-wait'
export default ({ Vue, app, store }) => {
Vue.use(VueWait)
app.wait = new VueWait({
useVuex: true,
registerDirective: true
})
export default ({ app }) => {
app.config.globalProperties.$initWait = () => {
const VueWait = createVueWait({
useVuex: true,
vuexModuleName: 'wait',
registerDirective: true
})
app.use(VueWait)
}
}

@ -1,15 +1,12 @@
import Vuelidate from 'vuelidate'
import _ from 'lodash'
export default ({ Vue, app }) => {
Vue.use(Vuelidate)
Vue.prototype.$errorMessage = (def) => {
app.config.globalProperties.$errorMessage = (def) => {
let message = null
_.forEach(def.$params, (param, paramName) => {
if (def[paramName] === false) {
message = app.i18n.t('validators.' + paramName) // TODO: does it work? we should recheck translations
if (def.$errors && def.$errors.length) {
if (def.$errors[0].$validator) {
message = app.i18n.global.tc('validators.' + def.$errors[0].$validator)
}
})
}
return message
}
}

@ -28,12 +28,12 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.csc-alert-error
background-color $negative
padding $flex-gutter-md
margin-bottom $flex-gutter-lg
background-color: $negative
padding: $flex-gutter-md
margin-bottom: $flex-gutter-lg
.csc-alert-error-text
line-height 1.4em
color $white
line-height: 1.4em
color: $white
</style>

@ -28,14 +28,14 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.csc-alert-info
background-color $info
padding $flex-gutter-md
margin-bottom $flex-gutter-lg
background-color: $info
padding: $flex-gutter-md
margin-bottom: $flex-gutter-lg
.csc-alert-icon
text-align left
text-align: left
.csc-alert-info-text
line-height 1.4em
color $white
line-height: 1.4em
color: $white
</style>

@ -80,6 +80,7 @@ export default {
default: false
}
},
emits: ['load', 'stopped', 'playing', 'loading'],
data () {
return {
playing: false,
@ -178,19 +179,19 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.audio-player
width 100%
display flex
justify-content space-around
align-items center
width: 100%
display: flex
justify-content: space-around
align-items: center
.control-btns
display flex
justify-content space-between
display: flex
justify-content: space-between
.progress-bar
margin-left 16px
margin-right 16px
margin-left: 16px
margin-right: 16px
</style>

@ -73,6 +73,7 @@ export default {
default: false
}
},
emits: ['click', 'remove', 'remove-all'],
data () {
return {}
},
@ -101,41 +102,41 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-dialpad
padding 16px
padding-bottom 0
<style lang="sass" rel="stylesheet/sass">
.csc-dialpad
padding: 16px
padding-bottom: 0
.csc-dialpad-btn
display flex
flex-direction column
margin-left 16px
.csc-dialpad-btn
display: flex
flex-direction: column
margin-left: 16px
.q-btn-inner
color: $dark
font-size: 22px
.q-btn-small
.q-btn-inner
color $dark
font-size 22px
.q-btn-small
.q-btn-inner
color $dark
font-size 18px
color: $dark
font-size: 18px
.csc-dialpad-btn.csc-dialpad-btn-main
.q-btn-inner
color white
.csc-dialpad-btn.csc-dialpad-btn-main
.q-btn-inner
color: white
.csc-dialpad-btn:first-child
margin-left 0
.csc-dialpad-btn:first-child
margin-left: 0
.csc-dialpad-btn-group
display: flex
flex-direction row
margin-bottom 8px
justify-content: center
.csc-dialpad-btn-group
display: flex
flex-direction: row
margin-bottom: 8px
justify-content: center
.csc-dialpad-btn-group.csc-dialpad-btn-group-special
justify-content: center
.q-btn
font-size 14px
.csc-dialpad-btn-group.csc-dialpad-btn-group-special
justify-content: center
.q-btn
font-size: 14px
.csc-dialpad-btn-group:last-child
margin-bottom 0
.csc-dialpad-btn-group:last-child
margin-bottom: 0
</style>

@ -9,17 +9,17 @@
@input="$emit('input')"
@hide="$emit('dialog-closed')"
>
<div
slot="content"
<template
#content
>
<csc-change-password-form
ref="changePasswordForm"
:loading="loading"
@validation-succeeded="validationSucceeded"
/>
</div>
<div
slot="actions"
</template>
<template
#actions
>
<q-btn
icon="check"
@ -31,7 +31,7 @@
>
{{ $t('Save') }}
</q-btn>
</div>
</template>
</csc-dialog>
</template>
<script>
@ -54,35 +54,36 @@ export default {
default: false
}
},
emits: ['change-password', 'dialog-closed', 'input'],
methods: {
validationSucceeded (payload) {
this.$emit('change-password', payload)
},
open () {
this.$refs.dialog.open()
this.$refs.dialog.show()
this.$refs.changePasswordForm.resetForm()
},
close () {
this.$refs.dialog.close()
this.$refs.dialog.hide()
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.csc-pbx-password-dialog
.csc-dialog-actions,
.csc-dialog-content
padding 15px
padding: 15px
.q-input
width 100%
min-width 270px
width: 100%
min-width: 270px
.q-if:before,
.q-icon
color white
color: white
.Password__strength-meter:after,
.Password__strength-meter:before
border-color #3b3440
border-color: #3b3440
.Password
width 100%
margin 20px 0px 30px
width: 100%
margin: 20px 0px 30px
</style>

@ -7,20 +7,23 @@
:opened="opened"
@close="onClose()"
>
<div
slot="content"
<template
#content
>
{{ message }}
</div>
<q-btn
slot="actions"
:icon="titleIcon"
:color="color"
flat
@click="confirm"
</template>
<template
#actions
>
{{ $t('Confirm') }}
</q-btn>
<q-btn
:icon="titleIcon"
:color="color"
flat
@click="confirm"
>
{{ $t('Confirm') }}
</q-btn>
</template>
</csc-dialog>
</template>
@ -58,16 +61,17 @@ export default {
default: 'primary'
}
},
emits: ['confirm', 'cancel', 'closed'],
data () {
return {
}
},
methods: {
open () {
this.$refs.dialogComp.open()
this.$refs.dialogComp.show()
},
close () {
this.$refs.dialogComp.close()
this.$refs.dialogComp.hide()
},
onClose () {
this.$emit('closed')
@ -84,5 +88,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -19,6 +19,7 @@
<script>
import { i18n } from 'src/boot/i18n'
import useValidate from '@vuelidate/core'
export default {
name: 'CscDataTableEditInput',
props: {
@ -36,11 +37,13 @@ export default {
},
saveLabel: {
type: String,
default: i18n.t('Save')
default: i18n.global.tc('Save')
}
},
emits: ['changed'],
data () {
return {
v$: useValidate(),
internalValue: this.value
}
},
@ -57,7 +60,7 @@ export default {
computed: {
error () {
if (this.column.componentValidations) {
return this.$v.internalValue.$error
return this.v$.internalValue.$errors.length > 0
} else {
return false
}
@ -65,7 +68,7 @@ export default {
errorMessage () {
if (this.column.componentValidations) {
const validation = this.column.componentValidations.find(validation =>
this.$v.internalValue[validation.name] === false
this.v$.internalValue[validation.name]?.$invalid === true
)
if (validation) {
return validation.error
@ -84,19 +87,19 @@ export default {
},
mounted () {
this.internalValue = this.value
this.$v.$reset()
this.v$.$reset()
},
methods: {
validate () {
if (this.column.componentValidations) {
this.$v.$touch()
return !this.$v.$invalid
this.v$.$touch()
return !this.v$.$invalid
} else {
return true
}
},
save () {
this.$v.$touch()
this.v$.$touch()
this.$emit('changed', {
column: this.column,
row: this.row,
@ -104,7 +107,7 @@ export default {
})
},
clear () {
this.$v.$reset()
this.v$.$reset()
}
}
}

@ -38,7 +38,7 @@
>
<template
v-if="column.componentIcon"
v-slot:prepend
#prepend
>
<q-icon
:name="column.componentIcon"
@ -72,9 +72,10 @@ export default {
},
saveLabel: {
type: String,
default: i18n.t('Save')
default: i18n.global.tc('Save')
}
},
emits: ['saved'],
data () {
return {
internalValue: this.value

@ -2,7 +2,6 @@
<q-dialog
ref="dialog"
v-bind="$attrs"
v-on="$listeners"
>
<q-card
class="bg-dark q-dialog-plugin"
@ -42,11 +41,11 @@
align="right"
>
<q-btn
v-close-popup
icon="clear"
color="white"
flat
:label="$t('Cancel')"
@click="cancel"
/>
<slot
name="actions"
@ -72,45 +71,14 @@ export default {
titleIconColor: {
type: String,
default: 'primary'
},
opened: {
type: Boolean,
default: false
}
},
watch: {
opened (opened) {
if (opened === true) {
this.open()
} else {
this.close()
}
}
},
mounted () {
if (this.opened) {
this.open()
}
},
methods: {
open () {
this.show()
},
show () {
this.$refs.dialog.show()
this.$emit('show')
},
close () {
this.hide()
this.$emit('close')
},
hide () {
this.$refs.dialog.hide()
this.$emit('hide')
},
cancel () {
this.close()
this.$emit('cancel')
}
}
}

@ -5,7 +5,7 @@
:title="title"
>
<template
v-slot:actions
#actions
>
<q-btn
icon="check"
@ -40,21 +40,18 @@ export default {
props: {
title: {
type: String,
default () { return this.$t('Change login password') }
required: true
},
passwordLabel: {
type: String,
default () {
return this.$t('Password')
}
required: true
},
passwordConfirmLabel: {
type: String,
default () {
return this.$t('Password confirm')
}
required: true
}
},
emits: ['ok'],
data () {
return {
ready: false,

@ -29,6 +29,7 @@ export default {
components: {
CscDialog
},
emits: ['hide'],
data () {
return {
dataImg: null

@ -4,12 +4,11 @@
inline-actions
rounded
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<template
v-if="icon !== null && icon !== undefined"
v-slot:avatar
#avatar
>
<q-icon
:name="icon"
@ -18,7 +17,7 @@
/>
</template>
<template
v-slot:action
#action
>
<slot
name="action"

@ -3,11 +3,10 @@
icon="error"
color="negative"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<template
v-slot:action
#action
>
<slot
name="action"

@ -3,11 +3,10 @@
icon="info"
color="info"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<template
v-slot:action
#action
>
<slot
name="action"

@ -3,11 +3,10 @@
icon="warning"
color="warning"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
<template
v-slot:action
#action
>
<slot
name="action"

@ -30,6 +30,7 @@ export default {
default: ''
}
},
emits: ['click'],
data () {
return {}
}

@ -39,7 +39,8 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-list-actions
margin-bottom $flex-gutter-sm
<style lang="sass" rel="stylesheet/sass">
.csc-list-actions
margin-bottom: $flex-gutter-sm
</style>

@ -24,6 +24,7 @@ export default {
default: ''
}
},
emits: ['click'],
data () {
return {}
},

@ -111,6 +111,7 @@ export default {
default: true
}
},
emits: ['toggle'],
data () {
return {
moreMenu: false
@ -139,64 +140,65 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-list-item-title-value,
.csc-list-item-title-keyword
margin-right $flex-gutter-xs
font-weight bold
vertical-align middle
.csc-list-item.csc-list-item-background
.csc-list-item-head
background-color $item-stripe-color
.csc-list-item
position relative
.csc-list-item-head
cursor pointer
padding $flex-gutter-sm
.csc-list-item-head-icon
padding 0
padding-right $flex-gutter-xs
padding-left $flex-gutter-xs
.csc-list-item-head-image
width 32px
height 32px
position relative
overflow hidden
img
position absolute
width 100%
.csc-list-item-head-title
padding-left $flex-gutter-sm
.csc-list-item-title
font-size 1rem
vertical-align middle
.csc-list-item-subtitle
margin-top 0.2 rem
font-size 90%
vertical-align middle
.csc-list-item-head-menu
.q-btn
padding 0
padding-left $flex-gutter-xs
padding-right $flex-gutter-xs
.q-btn-inner
i
margin 0
.csc-list-item-body
background-color $item-highlight-color
.csc-list-item-body-content
padding $flex-gutter-md
.csc-list-item.csc-list-item-expanded
.csc-list-item-head
background-color $item-highlight-color
.csc-list-item-head-icon
color $primary
.csc-list-item-head-title
.csc-list-item-title
color $primary
.csc-list-item-body
background-color $item-highlight-color
.csc-list-item-body-content
padding $flex-gutter-md
padding-top $flex-gutter-sm
<style lang="sass" rel="stylesheet/sass">
.csc-list-item-title-value,
.csc-list-item-title-keyword
margin-right: $flex-gutter-xs
font-weight: bold
vertical-align: middle
.csc-list-item.csc-list-item-background
.csc-list-item-head
background-color: $item-stripe-color
.csc-list-item
position: relative
.csc-list-item-head
cursor: pointer
padding: $flex-gutter-sm
.csc-list-item-head-icon
padding: 0
padding-right: $flex-gutter-xs
padding-left: $flex-gutter-xs
.csc-list-item-head-image
width: 32px
height: 32px
position: relative
overflow: hidden
img
position: absolute
width: 100%
.csc-list-item-head-title
padding-left: $flex-gutter-sm
.csc-list-item-title
font-size: 1rem
vertical-align: middle
.csc-list-item-subtitle
margin-top: 0.2 rem
font-size: 90%
vertical-align: middle
.csc-list-item-head-menu
.q-btn
padding: 0
padding-left: $flex-gutter-xs
padding-right: $flex-gutter-xs
.q-btn-inner
i
margin: 0
.csc-list-item-body
background-color: $item-highlight-color
.csc-list-item-body-content
padding: $flex-gutter-md
.csc-list-item.csc-list-item-expanded
.csc-list-item-head
background-color: $item-highlight-color
.csc-list-item-head-icon
color: $primary
.csc-list-item-head-title
.csc-list-item-title
color: $primary
.csc-list-item-body
background-color: $item-highlight-color
.csc-list-item-body-content
padding: $flex-gutter-md
padding-top: $flex-gutter-sm
</style>

@ -15,5 +15,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -31,5 +31,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -32,6 +32,7 @@ export default {
default: ''
}
},
emits: ['click'],
data () {
return {}
},
@ -43,5 +44,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -141,23 +141,23 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.csc-logo-light
.csc-logo-bubbles
path
stroke white !important
stroke: white !important
.csc-logo-phrase
path
fill white !important
fill: white !important
.csc-logo-ale
fill white !important
fill: white !important
.csc-logo-dark
.csc-logo-bubbles
path
stroke black !important
stroke: black !important
.csc-logo-phrase
path
fill black !important
fill: black !important
.csc-logo-ale
fill black !important
fill: black !important
</style>

@ -5,7 +5,7 @@
>
<q-expansion-item
v-if="item.visible && item.children && item.children.length > 0"
:key="index"
:key="index + '_childs'"
:label="item.label"
:icon="item.icon"
:content-inset-level="1"
@ -94,7 +94,7 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
.csc-word-space
white-space normal
white-space: normal
</style>

@ -13,9 +13,6 @@
</template>
<script>
import {
mapGetters
} from 'vuex'
import CscMainMenu from 'components/CscMainMenu'
export default {
name: 'CscMainMenuNewFeatures',

@ -96,9 +96,9 @@ export default {
mounted () {
this.assignStream(this.stream)
const fitMedia = () => { this.fitMedia() }
this.$root.$on('window-resized', fitMedia)
this.$root.$on('content-resized', fitMedia)
this.$root.$on('orientation-changed', fitMedia)
this.emitter.$on('window-resized', fitMedia)
this.emitter.$on('content-resized', fitMedia)
this.emitter.$on('orientation-changed', fitMedia)
this.$refs.media.addEventListener('playing', () => {
this.loading = false
this.fitMedia()
@ -202,21 +202,21 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-media
position relative
overflow hidden
.csc-media-spinner
position absolute
top 50%
left 50%
margin-top -12px
margin-left -12px
video.csc-media-video
position: absolute;
@media (max-width: $breakpoint-sm)
top 0px !important
width 100%
height 100%
object-fit cover
<style lang="sass" rel="stylesheet/sass">
.csc-media
position: relative
overflow: hidden
.csc-media-spinner
position: absolute
top: 50%
left: 50%
margin-top: -12px
margin-left: -12px
video.csc-media-video
position: absolute
@media (max-width: $breakpoint-sm)
top: 0px !important
width: 100%
height: 100%
object-fit: cover
</style>

@ -11,14 +11,14 @@
>
<slot />
<template
v-slot:grid-column-1
#grid-column-1
>
<slot
name="grid-column-1"
/>
</template>
<template
v-slot:grid-column-2
#grid-column-2
>
<slot
name="grid-column-2"

@ -1,6 +1,5 @@
<template>
<div
v-if="pageType === 'typeoutgoing'"
class="q-mb-lg"
>
<q-item
@ -13,17 +12,15 @@
no-wrap
>
<q-select
v-if="hasSubscriberProfileAttribute('ncos') && (this.isPbxAdmin || ! this.isPbxEnabled)"
v-if="hasSubscriberProfileAttribute('ncos') && (isPbxAdmin || !isPbxEnabled)"
v-model="ncosLevel"
use-chips
radio
emit-value
map-options
:options="ncosOptions"
:label="$t('Ncos')"
>
<template
v-slot:append
<template
#append
>
<csc-input-button-save
v-if="hasNcosChanged"
@ -36,17 +33,15 @@
</template>
</q-select>
<q-select
v-if="hasSubscriberProfileAttribute('ncos_set') && (this.isPbxAdmin || ! this.isPbxEnabled)"
v-if="hasSubscriberProfileAttribute('ncos_set') && (isPbxAdmin || !isPbxEnabled)"
v-model="ncosSet"
use-chips
radio
emit-value
map-options
:options="ncosSetOptions"
:label="$t('Ncos Set')"
>
<template
v-slot:append
<template
#append
>
<csc-input-button-save
v-if="hasNcosSetChanged"
@ -76,12 +71,6 @@ export default {
CscInputButtonSave,
CscInputButtonReset
},
props: {
pageType: {
type: String,
default: null
}
},
data () {
return {
ncosLevel: null,
@ -92,12 +81,6 @@ export default {
originalNcosSet: null
}
},
async created() {
await this.getNcosSubscriber();
await this.getCurrentNcosSubscriber();
await this.getNcosSetsSubscriber();
await this.getCurrentNcosSetsSubscriber();
},
computed: {
...mapGetters('user', [
'hasSubscriberProfileAttribute',
@ -109,7 +92,13 @@ export default {
},
hasNcosSetChanged () {
return this.ncosSet !== this.originalNcosSet
},
}
},
async created () {
await this.getNcosSubscriber()
await this.getCurrentNcosSubscriber()
await this.getNcosSetsSubscriber()
await this.getCurrentNcosSetsSubscriber()
},
async mounted () {
await this.getNcosSetSubscriber()
@ -131,37 +120,37 @@ export default {
this.ncosOptions = listNcos.map((ncos) => ({
label: ncos.label,
value: ncos.value
}));
}))
},
async getCurrentNcosSubscriber () {
const currentNcos = await this.getCurrentNcosLevelsSubscriber()
this.ncosLevel = currentNcos
this.originalNcosLevel = currentNcos;
this.originalNcosLevel = currentNcos
},
async getNcosSetsSubscriber () {
const listNcosSet = await this.getNcosSetSubscriber()
this.ncosSetOptions = listNcosSet.map((ncosSet) => ({
label: ncosSet.label,
value: ncosSet.value
}));
}))
},
async getCurrentNcosSetsSubscriber () {
const currentNcosSet = await this.getCurrentNcosSetSubscriber()
this.ncosSet = currentNcosSet
this.originalNcosSet = currentNcosSet;
this.originalNcosSet = currentNcosSet
},
save () {
save () {
if (this.hasNcosChanged) {
this.setNcosLevelSet({
ncosId: this.ncosLevel
})
this.originalNcosLevel = this.ncosLevel;
this.originalNcosLevel = this.ncosLevel
}
if (this.hasNcosSetChanged) {
this.setNcosSets({
ncosSetId: this.ncosSet
})
this.originalNcosSet = this.ncosSet;
this.originalNcosSet = this.ncosSet
}
},
resetNcos () {
@ -172,4 +161,4 @@ export default {
}
}
}
</script>
</script>

@ -1,7 +1,6 @@
<template>
<q-page
v-bind="$attrs"
v-on="$listeners"
>
<slot />
</q-page>
@ -10,7 +9,6 @@
<script>
import platformMixin from '../mixins/platform'
export default {
name: 'CscPage',
mixins: [
@ -26,6 +24,7 @@ export default {
default: false
}
},
emits: ['content-resized'],
data () {
return {}
},
@ -43,13 +42,13 @@ export default {
},
methods: {
resizeContent () {
this.$root.$emit('content-resized')
this.emitter.$emit('content-resized')
}
}
}
</script>
<!--<style lang="stylus" rel="stylesheet/stylus">-->
<!--<style lang="sass" rel="stylesheet/sass">-->
<!-- .csc-page-->
<!-- min-height 100vh-->
<!-- position relative-->

@ -44,6 +44,7 @@ export default {
components: {
CscPage
},
emits: ['input'],
data () {
return {
topMargin: 0

@ -14,14 +14,14 @@
class="full-width"
>
<q-tabs
:value="value"
:model-value="value"
style="z-index: 11"
align="center"
inline-label
active-color="primary"
dense
mobile-arrows
@input="input"
@update:model-value="input"
>
<slot
name="tabs"
@ -50,10 +50,12 @@ export default {
default: ''
}
},
emits: ['input'],
data () {
return {
topMargin: 0,
currentTab: ''
currentTab: '',
observer: null
}
},
computed: {
@ -65,14 +67,19 @@ export default {
},
mounted () {
this.computeTopMargin()
const observer = new MutationObserver(this.computeTopMargin)
observer.observe(this.$refs.pageSticky.$el, {
childList: true,
subtree: true
})
this.observer = observer
},
beforeUnmount () {
this.observer.disconnect()
},
methods: {
input ($event) {
async input ($event) {
this.$emit('input', $event)
this.computeTopMargin()
this.$nextTick(() => {
this.computeTopMargin()
})
},
computeTopMargin () {
this.topMargin = this.$refs.pageSticky.$el.offsetHeight

@ -5,7 +5,6 @@
<q-list
v-if="!gridView"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
</q-list>
@ -18,7 +17,6 @@
>
<q-list
v-bind="$attrs"
v-on="$listeners"
>
<slot
name="grid-column-1"
@ -30,7 +28,6 @@
>
<q-list
v-bind="$attrs"
v-on="$listeners"
>
<slot
name="grid-column-2"

@ -4,7 +4,6 @@
clickable
:disable="disable"
v-bind="$attrs"
v-on="$listeners"
>
<q-item-section
v-if="icon !== null"

@ -10,6 +10,7 @@
import CscPopupMenuItem from 'components/CscPopupMenuItem'
export default {
name: 'CscPopupMenuItemDelete',
components: { CscPopupMenuItem }
components: { CscPopupMenuItem },
emits: ['click']
}
</script>

@ -10,6 +10,7 @@
import CscPopupMenuItem from 'components/CscPopupMenuItem'
export default {
name: 'CscPopupMenuItemStartCall',
components: { CscPopupMenuItem }
components: { CscPopupMenuItem },
emits: ['click']
}
</script>

@ -3,24 +3,27 @@
ref="dialogComp"
:title="title"
:title-icon="titleIcon"
:opened="opened"
@cancel="cancel"
v-bind="$attrs"
>
<div
slot="content"
<template
#content
>
{{ message }}
</div>
<q-btn
slot="actions"
icon="delete"
data-cy="csc-dialog-delete"
color="negative"
flat
@click="remove"
</template>
<template
#actions
>
{{ $t('Remove') }}
</q-btn>
<q-btn
v-close-popup
icon="delete"
data-cy="csc-dialog-delete"
color="negative"
flat
@click="remove"
>
{{ $t('Remove') }}
</q-btn>
</template>
</csc-dialog>
</template>
@ -49,31 +52,25 @@ export default {
default: false
}
},
emits: ['ok', 'remove', 'cancel'],
data () {
return {
}
},
methods: {
show () {
this.open()
this.$refs.dialogComp.show()
},
open () {
this.$refs.dialogComp.open()
},
close () {
this.$refs.dialogComp.close()
hide () {
this.$refs.dialogComp.hide()
},
remove () {
this.close()
this.$emit('remove')
this.$emit('ok')
},
cancel () {
this.$emit('cancel')
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -7,7 +7,7 @@
@hide="resetForm()"
>
<template
v-slot:content
#content
>
<q-form>
<q-item>
@ -18,12 +18,12 @@
dense
:label="$t('Username')"
type="text"
:error="$v.username.$error"
:error-message="$errorMessage($v.username)"
@blur="$v.username.$touch()"
:error="v$.username.$errors.length > 0"
:error-message="$errorMessage(v$.username)"
@blur="v$.username.$touch()"
>
<template
v-slot:prepend
#prepend
>
<q-icon
name="fas fa-user-cog"
@ -35,7 +35,7 @@
</q-form>
</template>
<template
v-slot:actions
#actions
>
<q-btn
icon="check"
@ -53,12 +53,13 @@
<script>
import {
required
} from 'vuelidate/lib/validators'
} from '@vuelidate/validators'
import {
mapActions,
mapState
} from 'vuex'
import CscDialog from './CscDialog'
import useValidate from '@vuelidate/core'
export default {
name: 'CscRetrievePasswordDialog',
components: {
@ -70,8 +71,10 @@ export default {
default: false
}
},
emits: ['input', 'close'],
data () {
return {
v$: useValidate(),
username: ''
}
},
@ -90,8 +93,8 @@ export default {
'resetPassword'
]),
async submit () {
this.$v.$touch()
if (!this.$v.$invalid) {
this.v$.$touch()
if (!this.v$.$invalid) {
try {
const res = await this.resetPassword({
username: this.username,
@ -116,7 +119,7 @@ export default {
}
},
resetForm () {
this.$v.$reset()
this.v$.$reset()
this.username = ''
}
}

@ -33,7 +33,7 @@
<script>
import _ from 'lodash'
import { i18n } from 'boot/i18n'
import { getLanguageLabels, setLanguage } from 'src/i18n'
import { setLanguage } from 'src/i18n'
export default {
name: 'CscSelectionLanguage',
props: {
@ -44,16 +44,37 @@ export default {
},
computed: {
languageLabel () {
const lang = _.first(this.options.filter(item => item.value === i18n.locale))
const lang = _.first(this.options.filter(item => item.value === i18n.global.locale))
return this.$t('Language') + ' (' + lang.label + ')'
},
options () {
return getLanguageLabels()
return [
{
value: 'en-US',
label: this.$t('English', 'en-US')
},
{
value: 'de',
label: this.$t('German', 'de')
},
{
value: 'es',
label: this.$t('Spanish', 'es')
},
{
value: 'fr',
label: this.$t('French', 'fr')
},
{
value: 'it',
label: this.$t('Italian', 'it')
}
]
}
},
methods: {
changeLanguage (lang) {
setLanguage(lang)
async changeLanguage (lang) {
await setLanguage(lang)
}
}
}

@ -29,7 +29,7 @@
<script>
import _ from 'lodash'
import { i18n } from 'boot/i18n'
import { getLanguageLabels, setLanguage } from 'src/i18n'
import { setLanguage } from 'src/i18n'
export default {
// TODO: this component has some duplicated code with "CscSelectionLanguage" component. Please recheck do we still need to have a separate UI for Mobile users
@ -45,7 +45,28 @@ export default {
return this.$t('Language') + ' (' + lang.label + ')'
},
options () {
return getLanguageLabels()
return [
{
value: 'en-US',
label: i18n.global.tc('English', 'en-US')
},
{
value: 'de',
label: i18n.global.tc('German', 'de')
},
{
value: 'es',
label: i18n.global.tc('Spanish', 'es')
},
{
value: 'fr',
label: i18n.global.tc('French', 'fr')
},
{
value: 'it',
label: i18n.global.tc('Italian', 'it')
}
]
}
},
methods: {

@ -1,8 +1,7 @@
<template>
<q-dialog
:value="value"
ref="faxDialog"
v-bind="$attrs"
v-on="$listeners"
>
<q-card
style="min-width: 50%"
@ -14,7 +13,6 @@
</q-card-section>
<q-card-section>
<csc-call-input
v-if="value"
v-model="form.destination"
:label="$t('Destination Number')"
data-cy="sendfax-destinationnumber"
@ -35,10 +33,10 @@
type="text"
:label="$t('Page Header')"
data-cy="sendfax-pageheader"
:error="$v.form.pageHeader.$error"
:error="v$.form.pageHeader.$errors.length > 0"
:error-message="pageHeaderErrorMessage"
@input="$v.form.pageHeader.$touch"
@blur="$v.form.pageHeader.$touch"
@update:model-value="v$.form.pageHeader.$touch()"
@blur="v$.form.pageHeader.$touch()"
/>
<q-input
v-model="form.data"
@ -48,15 +46,15 @@
:min-rows="10"
:label="$t('Content')"
data-cy="sendfax-content"
:error="$v.form.data.$error"
:error="v$.form.data.$errors.length > 0"
:error-message="dataErrorMessage"
@input="$v.form.data.$touch"
@blur="$v.form.data.$touch"
@update:model-value="v$.form.data.$touch()"
@blur="v$.form.data.$touch()"
/>
<csc-input-file
accept=".pdf,.tif,.tiff,.txt,.ps"
@file-selected="toggleFileSelected"
data-cy="sendfax-fileinput"
@file-selected="toggleFileSelected"
/>
</q-card-section>
<q-card-actions>
@ -67,7 +65,7 @@
color="default"
:label="$t('Cancel')"
data-cy="sendfax-cancel"
@click="resetFormData"
@click="hide"
/>
<q-btn
flat
@ -92,8 +90,9 @@ import {
required,
requiredUnless,
maxLength
} from 'vuelidate/lib/validators'
} from '@vuelidate/validators'
import CscInputFile from 'components/form/CscInputFile'
import useValidate from '@vuelidate/core'
export default {
name: 'CscSendFax',
@ -101,17 +100,18 @@ export default {
CscInputFile,
CscCallInput
},
props: {
value: {
type: Boolean,
default: false
}
},
data () {
return {
form: {},
form: {
destination: '',
pageHeader: null,
data: null,
quality: this.$faxQualityOptionsDefault.value,
faxfile: null
},
isMobile: this.$q.platform.is.mobile,
destinationError: false
destinationError: false,
v$: useValidate()
}
},
validations: {
@ -138,29 +138,30 @@ export default {
return (this.form.data && this.form.data.length > 0) || this.form.faxfile
},
formDisabled () {
return !this.$v.form.$anyDirty ||
return !this.v$.form.$anyDirty ||
!this.form.pageHeader ||
!this.form.destination ||
this.destinationError ||
this.$v.form.pageHeader.$error ||
this.v$.form.pageHeader.$errors.length > 0 ||
!this.hasContentToSend
},
pageHeaderErrorMessage () {
return this.$t('{field} must have at most {maxLength} letters', {
field: this.$t('Page Header'),
maxLength: this.$v.form.pageHeader.$params.maxLength.max
maxLength: this.v$.form.pageHeader.maxLength.$params.max
})
},
dataErrorMessage () {
if (!this.$v.form.data.required) {
const errorsTab = this.v$.form.data.$errors
if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'required') {
return this.$t('{fieldOne} or {fieldTwo} is required', {
fieldOne: this.$t('Content'),
fieldTwo: this.$t('File')
})
} else if (!this.$v.form.data.maxLength) {
} else if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'maxLength') {
return this.$t('{field} must have at most {maxLength} letters', {
field: this.$t('Content'),
maxLength: this.$v.form.data.$params.maxLength.max
maxLength: this.v$.form.data.maxLength.$params.max
})
} else {
return ''
@ -181,7 +182,7 @@ export default {
this.form.faxfile = value
},
sendFax () {
if (this.$v.form.$error ||
if (this.v$.form.$errors.length > 0 ||
this.destinationError) {
showGlobalError(this.$t('You have invalid form input. Please check and try again.'))
} else {
@ -200,48 +201,54 @@ export default {
quality: this.$faxQualityOptionsDefault.value,
faxfile: null
}
this.$v.$reset()
this.v$.$reset()
},
show () {
this.$refs.faxDialog.show()
},
hide () {
this.resetFormData()
this.$refs.faxDialog.hide()
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
#fax-modal
.modal-content
min-width 40vw
padding 20px 15px
<style lang="sass" rel="stylesheet/sass">
#fax-modal
.modal-content
min-width: 40vw
padding: 20px 15px
.title
line-height $csc-subtitle-line-height
font-size $csc-subtitle-font-size
font-weight $csc-subtitle-font-weight
.upload-field
margin-bottom 10px
.title
line-height: $csc-subtitle-line-height
font-size: $csc-subtitle-font-size
.upload-field
margin-bottom: 10px
.upload-label
display block
font-size 16px
margin-bottom 5px
.upload-label
display: block
font-size: 16px
margin-bottom: 5px
.upload-button
color black
.upload-button
color: black
.reset-button
padding 0
.reset-button
padding: 0
.q-icon
margin 0
.q-icon
margin: 0
.upload-filename
color black
.upload-filename
color: black
#fax-file-upload
display none
#fax-file-upload
display: none
#csc-error-label
font-size 12px
color $negative
margin -15px 0 10px 0
#csc-error-label
font-size: 12px
color: $negative
margin: -15px 0 10px 0
</style>

@ -4,7 +4,6 @@
:delay="delay"
:content-class="contentClass"
v-bind="$attrs"
v-on="$listeners"
@show="autoHide"
@hide="cancelAutoHide"
>
@ -34,7 +33,7 @@ export default {
autoHideHandler: undefined
}
},
beforeDestroy () {
beforeUnmount () {
this.cancelAutoHide()
},
methods: {

@ -57,8 +57,8 @@
<q-item
v-ripple
clickable
@click="logout"
data-cy="user-logout"
@click="logout"
>
<q-item-section
side
@ -124,5 +124,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -2,7 +2,7 @@
<q-item>
<q-item-section>
<q-select
v-model="selectedLanguage"
:model-value="selectedLanguage"
emit-value
map-options
:disable="loading"
@ -11,7 +11,7 @@
data-cy="voicebox-change-language"
:title="$t('Voice prompts language for voicemail, conference and application server')"
:options="languageOptions"
v-on="$listeners"
@update:model-value="$emit('input', $event)"
>
<template
#prepend

@ -5,7 +5,6 @@
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
v-on="$listeners"
>
<slot />
</q-popup-proxy>
@ -22,6 +21,7 @@ import {
} from 'vuex'
export default {
name: 'CscCfConditionPopup',
emits: ['open', 'close'],
data () {
return {
popupId: _.kebabCase(this.$options.name) + '-' + v4()

@ -17,7 +17,7 @@ export default {
name: 'CscCfDestination',
mixins: [destination],
props: {
value: {
modelValue: {
type: Object,
default: undefined
},
@ -38,8 +38,8 @@ export default {
destinationIcon () {
if (this.icon) {
return this.icon
} else if (this.value?.destination) {
return this.destinationIconBySipUri(this.value.destination)
} else if (this.modelValue?.destination) {
return this.destinationIconBySipUri(this.modelValue.destination)
} else {
return ''
}
@ -47,8 +47,8 @@ export default {
destinationLabel () {
if (this.label) {
return this.label
} else if (this.value?.destination) {
return this.destinationFormattedBySipUri(this.value.destination)
} else if (this.modelValue?.destination) {
return this.destinationFormattedBySipUri(this.modelValue.destination)
} else {
return ''
}
@ -58,10 +58,12 @@ export default {
'q-pl-xs',
'text-weight-bold',
'text-no-wrap',
...(this.clickable ? [
'cursor-pointer',
'text-primary'
] : [])
...(this.clickable
? [
'cursor-pointer',
'text-primary'
]
: [])
]
}
}

@ -1,18 +1,19 @@
<template>
<csc-cf-destination
:value="destination"
:model-value="destination"
:label="announcement ? announcement.label : ''"
:clickable="true"
>
<q-popup-edit
v-slot="scope"
v-model="announcement"
buttons
anchor="top left"
@before-show="$store.commit('callForwarding/popupShow', null)"
@save="$emit('input', announcement)"
@save="$emit('input', $event)"
>
<q-select
v-model="announcement"
v-model="scope.value"
map-options
:rules="[ checkAnnouncement ]"
:options="announcements"
@ -38,6 +39,7 @@ export default {
default: undefined
}
},
emits: ['input'],
data () {
return {
announcement: this.$attrs.value

@ -1,21 +1,22 @@
<template>
<csc-cf-destination
:value="destination"
:model-value="destination"
:label="destination.simple_destination === ' ' ? $t('Number') : destination.simple_destination"
:clickable="true"
>
<q-popup-edit
v-slot="scope"
v-model="number"
buttons
@before-show="$store.commit('callForwarding/popupShow', null)"
@save="$emit('input', $event)"
>
<csc-input
v-model="number"
v-model="scope.value"
dense
>
<template
v-slot:prepend
#prepend
>
<q-icon
name="phone_forwarded"
@ -38,6 +39,7 @@ export default {
default: undefined
}
},
emits: ['input'],
data () {
return {
number: this.$attrs.value

@ -85,6 +85,7 @@ export default {
type: Boolean,
default: false
}
}
},
emits: ['close', 'back']
}
</script>

@ -4,7 +4,7 @@
icon="today"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
@close="$emit('close')"
>
<q-date
v-model="selectedDate"
@ -14,7 +14,7 @@
minimal
/>
<template
v-slot:actions
#actions
>
<q-btn
v-if="deleteButton"
@ -72,9 +72,10 @@ export default {
default: ''
}
},
emits: ['close'],
data () {
return {
selectedDate: this.formattedDate
selectedDate: null
}
},
computed: {

@ -4,7 +4,7 @@
icon="book_online"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
@close="$emit('close')"
>
<template
v-if="invalidDateset"
@ -14,7 +14,7 @@
dense
class="bg-red-8 text-white q-pt-md q-ma-md half-screen-width"
>
<template v-slot:avatar>
<template #avatar>
<q-icon name="date_range" />
</template>
{{ $t('The "{timeset}" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.', { timeset: timeSet.name }) }}
@ -33,7 +33,7 @@
/>
</template>
<template
v-slot:actions
#actions
>
<q-btn
v-if="deleteButton"
@ -92,6 +92,7 @@ export default {
default: ''
}
},
emits: ['close'],
data () {
return {
invalidDateset: false,

@ -1,7 +1,6 @@
<template>
<csc-cf-group-condition
title=" "
v-on="$listeners"
>
<q-list
dense
@ -81,6 +80,7 @@ export default {
type: Object,
default: undefined
}
}
},
emits: ['step']
}
</script>

@ -15,7 +15,7 @@
dense
class="bg-red-8 text-white q-pt-md q-ma-md half-screen-width"
>
<template v-slot:avatar>
<template #avatar>
<q-icon name="date_range" />
</template>
{{ $t('The "{timeset}" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.', { timeset: timeSet.name }) }}
@ -31,7 +31,7 @@
v-model="sameTimes"
:label="$t('Same time for selected days')"
data-cy="csc-office-hours-sametime"
:disable="$v.$invalid"
:disable="v$.$invalid"
/>
</div>
<div
@ -39,9 +39,10 @@
>
<q-item-section>
<csc-cf-selection-weekdays
v-model="weekdays"
:weekdays="weekdays"
:tabs="!sameTimes"
:disable="$v.$invalid"
:disable="v$.$invalid"
@input="weekdays=$event"
/>
</q-item-section>
</div>
@ -50,23 +51,23 @@
class="scroll time-range-list-height"
>
<q-item
v-for="(v, index) in $v.currentDayTimeRanges.$each.$iter"
v-for="(currentDayTimeRange, index) in currentDayTimeRanges"
:key="index"
>
<q-item-section>
<csc-input
v-model="v.from.$model"
v-model="currentDayTimeRange.from"
dense
:label="$t('Start time')"
data-cy="csc-office-hours-starttime"
mask="##:##"
fill-mask
:disable="disabled"
:error="v.from.$invalid"
:error-message="timeValidationErrMsg(v.from)"
:error="v$.$error && v$.currentDayTimeRanges.$each.$response.$errors[index].from.length > 0"
:error-message="timeValidationErrMsg(v$.currentDayTimeRanges.$each.$response.$errors[index].from)"
>
<template
v-slot:append
#append
>
<q-btn
icon="access_time"
@ -78,14 +79,14 @@
ref="startTimePopup"
>
<q-time
v-model="v.from.$model"
v-model="currentDayTimeRange.from"
flat
now-btn
square
format24h
text-color="dark"
color="primary"
@input="$refs.startTimePopup[index].hide()"
@update:model-value="$refs.startTimePopup[index].hide()"
/>
</q-popup-proxy>
</q-btn>
@ -94,18 +95,18 @@
</q-item-section>
<q-item-section>
<csc-input
v-model="v.to.$model"
v-model="currentDayTimeRange.to"
dense
:label="$t('End time')"
data-cy="csc-office-hours-endtime"
mask="##:##"
fill-mask
:disable="disabled"
:error="v.to.$invalid"
:error-message="timeValidationErrMsg(v.to)"
:error="v$.$error && v$.currentDayTimeRanges.$each.$response.$errors[index].to.length > 0"
:error-message="timeValidationErrMsg(v$.currentDayTimeRanges.$each.$response.$errors[index].to)"
>
<template
v-slot:append
#append
>
<q-btn
icon="access_time"
@ -117,14 +118,14 @@
ref="endTimePopup"
>
<q-time
v-model="v.to.$model"
v-model="currentDayTimeRange.to"
flat
now-btn
square
format24h
text-color="dark"
color="primary"
@input="$refs.endTimePopup[index].hide()"
@update:model-value="$refs.endTimePopup[index].hide()"
/>
</q-popup-proxy>
</q-btn>
@ -159,7 +160,7 @@
</div>
</template>
<template
v-slot:actions
#actions
>
<q-btn
v-if="deleteButton"
@ -178,7 +179,7 @@
flat
color="primary"
icon="check"
:disable="disabled || $v.$invalid"
:disable="disabled || v$.$invalid"
@click="createTimeSetOfficeHoursEvent"
/>
</template>
@ -197,8 +198,8 @@ import {
kamailioTimesetToHuman, timeStrToMinutes
} from 'src/helpers/kamailio-timesets-converter'
import { showGlobalError, showGlobalWarning } from 'src/helpers/ui'
import { or } from 'vuelidate/lib/validators'
import { or, helpers } from '@vuelidate/validators'
import useValidate from '@vuelidate/core'
function isTimeStrEmpty (val) {
return val === '' || val === '__:__'
}
@ -236,18 +237,20 @@ export default {
default: ''
}
},
emits: ['close', 'back'],
data () {
return {
invalidTimeset: false,
sameTimes: true,
weekdays: DEFAULT_WEEKDAYS,
timeRangesByDay: this.getInitialTimeRanges(),
timeRangesForAll: [this.getEmptyTimeRange()]
timeRangesForAll: [this.getEmptyTimeRange()],
v$: useValidate()
}
},
validations: {
currentDayTimeRanges: {
$each: {
$each: helpers.forEach({
from: {
validTime: or(isTimeStrEmpty, isTimeStrValid),
bothFilled: (val, vm) => (isTimeStrEmpty(val) && isTimeStrEmpty(vm.to)) || (!isTimeStrEmpty(val) && !isTimeStrEmpty(vm.to))
@ -256,7 +259,7 @@ export default {
validTime: or(isTimeStrEmpty, isTimeStrValid),
notInversed: (val, vm) => isTimeStrEmpty(vm.from) || !isTimeStrValid(vm.from) || isTimeStrEmpty(vm.to) || (isTimeStrValid(vm.to) && timeStrToMinutes(vm.from) <= timeStrToMinutes(vm.to))
}
}
})
}
},
computed: {
@ -398,13 +401,13 @@ export default {
this.currentDayTimeRanges.splice(index, 1)
},
timeValidationErrMsg (field) {
if (!field.validTime) {
if (field.length && field[0].$validator === 'validTime') {
return this.$t('Time is invalid')
}
if (field.bothFilled === false) {
if (field.length && field[0].$validator === 'bothFilled') {
return this.$t('Start and End time should be set')
}
if (field.notInversed === false) {
if (field.length && field[0].$validator === 'notInversed') {
return this.$t('Start time should be less than End time')
}
},
@ -480,10 +483,10 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
<style lang="sass" rel="stylesheet/sass" scoped>
.time-range-list-height
// NOTE: 65vh is a default max-height for q-dialog. Other magic numbers are sum of another elements heights, like headers, buttons etc
max-height calc(65vh - 250px)
max-height: calc(65vh - 250px)
</style>

@ -3,7 +3,7 @@
:title="title"
:loading="$wait.is('csc-cf-source-set-create')"
v-bind="$attrs"
v-on="$listeners"
@close="$emit('close')"
>
<q-list
class="no-margin q-pa-md"
@ -66,7 +66,7 @@
</q-item>
</q-list>
<template
v-slot:actions
#actions
>
<q-btn
v-if="deleteButton"
@ -159,10 +159,11 @@ export default {
default: null
}
},
emits: ['close', 'select'],
data () {
return {
sourceSetNameInternal: this.sourceSetName,
sourceSetNumbersInternal: this.sourceSetNumbers
sourceSetNameInternal: null,
sourceSetNumbersInternal: null
}
},
computed: {

@ -3,7 +3,7 @@
:title="title"
:loading="$wait.is('csc-cf-source-set-create')"
v-bind="$attrs"
v-on="$listeners"
@close="$emit('close')"
>
<div
class="no-margin q-pa-md"
@ -17,7 +17,7 @@
/>
</div>
<template
v-slot:actions
#actions
>
<q-btn
:label="createLabel"
@ -89,6 +89,7 @@ export default {
default: ''
}
},
emits: ['close', 'create'],
data () {
return {
selectedSourceSet: null

@ -4,14 +4,15 @@
icon="calendar_today"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
@close="$emit('close')"
>
<csc-cf-selection-weekdays
v-model="selectedWeekdays"
:weekdays="selectedWeekdays"
class="q-pl-md q-pr-md q-pt-sm q-pb-sm"
@input="selectedWeekdays=$event"
/>
<template
v-slot:actions
#actions
>
<q-btn
v-if="deleteButton"
@ -70,9 +71,10 @@ export default {
default: ''
}
},
emits: ['close'],
data () {
return {
selectedWeekdays: this.weekdays
selectedWeekdays: null
}
},
computed: {

@ -8,9 +8,8 @@
/>
</q-item-section>
<q-item-section
:disabled="loading || !mapping.enabled"
>
:class="loading || !mapping.enabled ? 'disabled' : ''"
>
<q-item-label>
<template
v-if="destinationIndex === 0 && mapping.type !== 'cft'"
@ -42,7 +41,7 @@
dense
>
<template
v-slot:prepend
#prepend
>
<q-icon
name="access_time"
@ -67,22 +66,23 @@
{{ destinationPrevious.timeout }}
{{ $t('seconds') }}
<q-popup-edit
v-slot="scope"
v-model="changedDestinationTimeout"
buttons
@before-show="$store.commit('callForwarding/popupShow', null)"
@save="updateDestinationTimeoutEvent({
destinationTimeout: changedDestinationTimeout,
destinationTimeout: $event,
destinationIndex: destinationIndex - 1,
destinationSetId: destinationSet.id
})"
>
<csc-input
v-model="changedDestinationTimeout"
v-model="scope.value"
type="number"
dense
>
<template
v-slot:prepend
#prepend
>
<q-icon
name="access_time"
@ -95,26 +95,26 @@
</template>
<csc-cf-destination-custom-announcement
v-if="isDestinationTypeCustomAnnouncement(destination.destination) && destination.announcement_id"
v-model="announcement"
:value="announcement"
:destination="destination"
:announcements="announcements"
@input="updateAnnouncementEvent({
destinationIndex: destinationIndex,
destinationSetId: destinationSet.id
})"
}, $event)"
/>
<csc-cf-destination-number
v-else-if="isDestinationTypeNumber(destination.destination)"
v-model="changedDestination"
:value="changedDestination"
:destination="destination"
@input="updateDestinationEvent({
destinationIndex: destinationIndex,
destinationSetId: destinationSet.id
})"
}, $event)"
/>
<csc-cf-destination
v-else
:value="destination"
:model-value="destination"
/>
</q-item-label>
</q-item-section>
@ -199,6 +199,7 @@ export default {
default: ''
}
},
emits: ['delete-last'],
data () {
return {
changedDestination: this.destination.simple_destination,
@ -242,10 +243,10 @@ export default {
'getAnnouncementById',
'updateAnnouncement'
]),
async updateDestinationEvent (payload) {
async updateDestinationEvent (payload, newDestination) {
this.$wait.start(this.waitIdentifier)
try {
const validatedDest = await this.rewriteDestination(this.changedDestination)
const validatedDest = await this.rewriteDestination(newDestination)
await this.updateDestination({ ...payload, destination: validatedDest })
} catch (err) {
showGlobalError(err.message)
@ -278,16 +279,16 @@ export default {
},
async updateRingTimeoutEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.updateRingTimeout({ringTimeout: this.changedDestinationTimeout, subscriberId: this.subscriberId})
await this.updateRingTimeout({ ringTimeout: this.changedDestinationTimeout, subscriberId: this.subscriberId })
this.$wait.end('csc-cf-mappings-full')
},
setAnnouncement () {
this.announcement = _.first(this.announcements.filter(announcement => announcement.value === this.destination.announcement_id))
},
async updateAnnouncementEvent (payload) {
async updateAnnouncementEvent (payload, newAnnouncement) {
this.$wait.start(this.waitIdentifier)
try {
await this.updateAnnouncement({ ...payload, announcementId: this.announcement.value })
await this.updateAnnouncement({ ...payload, announcementId: newAnnouncement.value })
this.setAnnouncement()
} catch (err) {
showGlobalError(err.message)

@ -53,7 +53,7 @@ export default {
primaryNumberSource: {
type: Object,
default: undefined
},
}
},
data () {
return {

@ -29,12 +29,12 @@
<template
v-if="sourceSet.mode === 'whitelist'"
>
{{ $t('and call from') }}
{{ ' ' + $t('and call from') + ' ' }}
</template>
<template
v-else
>
{{ $t('and call not from') }}
{{ ' ' + $t('and call not from') + ' ' }}
</template>
<span
:class="clickableClasses"
@ -72,7 +72,7 @@
<template
v-if="timeSet"
>
{{ $t('and') }}
{{ ' ' + $t('and') + ' ' }}
<template
v-if="timeSet.name.startsWith('csc-date-exact')"
>
@ -84,7 +84,7 @@
<q-icon
name="today"
/>
{{ timeSet.times | timeSetDateExact }}
{{ $filters.timeSetDateExact(timeSet.times) }}
<csc-cf-condition-popup-date
data-cy="csc-condtion-date"
:mapping="mapping"
@ -106,7 +106,7 @@
<q-icon
name="book_online"
/>
{{ timeSet.times | timeSetDateRange }}
{{ $filters.timeSetDateRange(timeSet.times) }}
<csc-cf-condition-popup-date-range
data-cy="csc-condtion-date-range"
:mapping="mapping"
@ -128,7 +128,7 @@
<q-icon
name="calendar_today"
/>
{{ timeSet.times | timeSetWeekdays }}
{{ $filters.timeSetWeekdays(timeSet.times) }}
<csc-cf-condition-popup-weekdays
data-cy="csc-condtion-weekdays"
:mapping="mapping"
@ -150,7 +150,7 @@
<q-icon
name="access_time"
/>
{{ timeSet.times | timeSetOfficeHoursSameTime }}
{{ $filters.timeSetOfficeHoursSameTime(timeSet.times) }}
<csc-cf-condition-popup-office-hours
data-cy="csc-condtion-office-hours"
:mapping="mapping"
@ -165,14 +165,14 @@
v-else
:class="clickableClasses"
>
{{ timeSet.times | timeSetTimes }}
{{ $filters.timeSetTimes(timeSet.times) }}
</span>
</template>
<template
v-if="!sourceSet || !timeSet"
>
<span>
{{ $t('and') }}
{{ ' ' + $t('and') + ' ' }}
</span>
<span
:class="clickableClasses"
@ -201,7 +201,7 @@
:grid-view="true"
>
<template
v-slot:grid-column-1
#grid-column-1
>
<csc-popup-menu-item
v-if="mapping.type === 'cfu' && hasSubscriberProfileAttribute('cft')"
@ -282,7 +282,7 @@
/>
</template>
<template
v-slot:grid-column-2
#grid-column-2
>
<csc-popup-menu-item
v-if="isPbxAttendant && platformInfo.cloudpbx"
@ -458,8 +458,8 @@ export default {
},
async toggleMappingEvent (mapping) {
this.$wait.start(this.waitIdentifier)
let mappingWithSubscriberId = Object.assign({}, mapping);
mappingWithSubscriberId["subscriberId"] = this.subscriberId
const mappingWithSubscriberId = Object.assign({}, mapping)
mappingWithSubscriberId.subscriberId = this.subscriberId
await this.toggleMapping(mappingWithSubscriberId)
this.$wait.end(this.waitIdentifier)
},
@ -472,20 +472,20 @@ export default {
persistent: true
}).onOk(async data => {
this.$wait.start(this.waitIdentifier)
let mappingWithSubscriberId = Object.assign({}, mapping);
mappingWithSubscriberId["subscriberId"] = this.subscriberId
const mappingWithSubscriberId = Object.assign({}, mapping)
mappingWithSubscriberId.subscriberId = this.subscriberId
await this.deleteMapping(mappingWithSubscriberId)
this.$wait.end(this.waitIdentifier)
})
},
async ringPrimaryNumberEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.ringPrimaryNumber({subscriberId: this.subscriberId})
await this.ringPrimaryNumber({ subscriberId: this.subscriberId })
this.$wait.end('csc-cf-mappings-full')
},
async doNotRingPrimaryNumberEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.doNotRingPrimaryNumber({subscriberId: this.subscriberId})
await this.doNotRingPrimaryNumber({ subscriberId: this.subscriberId })
this.$wait.end('csc-cf-mappings-full')
}
}

@ -59,7 +59,7 @@ import {
export default {
name: 'CscCfSelectionWeekdays',
props: {
value: {
weekdays: {
type: Array,
default: () => [DAY_MAP[0]]
},
@ -72,9 +72,10 @@ export default {
default: false
}
},
emits: ['input'],
data () {
return {
selectedWeekdays: this.value
selectedWeekdays: this.weekdays
}
},
computed: {
@ -101,7 +102,7 @@ export default {
selectedWeekdays (weekdays) {
this.$emit('input', weekdays)
},
value (weekdays) {
weekdays (weekdays) {
this.selectedWeekdays = weekdays
}
},
@ -112,17 +113,21 @@ export default {
} else {
this.selectedWeekdays.push(day.value)
}
this.$emit('input', this.selectedWeekdays)
},
isSelected (day) {
return this.selectedWeekdays.find(dayValue => day.value === dayValue)
if (this.selectedWeekdays) {
return this.selectedWeekdays.find(dayValue => day.value === dayValue)
}
return false
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
<style lang="sass" rel="stylesheet/sass" scoped>
.weekdays-selection-component
// Note: the magic number for height is the max component height in buttons mode.
// It makes the height of our component stable in any mode (tab \ buttons)
min-height 42px
min-height: 42px
</style>

@ -8,7 +8,6 @@
input-debounce="300"
v-bind="$attrs"
@filter="filter"
v-on="$listeners"
/>
</template>
<script>

@ -61,7 +61,7 @@
v-else-if="isEnded"
class="csc-call-error"
>
{{ endedReason | startCase }} ({{ callNumberFormatted || callNumberQuery }})
{{ $filters.startCase(endedReason) }} ({{ callNumberFormatted || callNumberQuery }})
</div>
</div>
</div>
@ -297,7 +297,7 @@
<div
class="csc-call-info-phrase"
>
{{ endedReason | startCase }}
{{ $filters.startCase(endedReason) }}
</div>
<div
class="csc-call-info-number"
@ -421,6 +421,7 @@ export default {
default: false
}
},
emits: ['toggle-screen', 'toggle-camera', 'minimize-call', 'maximize-call', 'click-dialpad', 'toggle-remote-volume', 'toggle-microphone', 'toggle-dialpad', 'close-call', 'end-call', 'accept-call', 'start-call'],
data () {
return {
localMediaWrapperWidth: 0,
@ -567,9 +568,9 @@ export default {
this.fetchRemoteMediaWrapperWidth()
}
fetchMediaWrapperWidth()
this.$root.$on('window-resized', fetchMediaWrapperWidth)
this.$root.$on('content-resized', fetchMediaWrapperWidth)
this.$root.$on('orientation-changed', fetchMediaWrapperWidth)
this.emitter.$on('window-resized', fetchMediaWrapperWidth)
this.emitter.$on('content-resized', fetchMediaWrapperWidth)
this.emitter.$on('orientation-changed', fetchMediaWrapperWidth)
},
methods: {
fetchLocalMediaWrapperWidth () {
@ -651,202 +652,203 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-call
left $layout-aside-left-width
top 0
position fixed
bottom 0
right 0
z-index 40
.q-btn.q-btn-round
box-shadow none
.csc-call-content
position absolute
bottom $call-footer-height
top 0
right 0
left 0
z-index 2
background-color $secondary
.csc-call-info
.csc-call-info-content
margin-top -80px
.csc-call-error
color $negative
.csc-call-spinner
margin-bottom $flex-gutter-sm
.csc-call-media-local
position absolute
bottom $flex-gutter-sm
left $flex-gutter-sm
width 20%
height auto
overflow hidden
z-index 2
font-size 0
.csc-call-info-established
position absolute
bottom $call-footer-action-margin * 2
right 0
left 0
justify-items center
z-index 3
color white
.csc-media-icon
margin-right $flex-gutter-xs
.csc-dialpad-button
vertical-align center
margin-left $flex-gutter-sm
.csc-call-media-remote
position absolute
top 0
bottom $call-footer-height
right 0
left 0
z-index 1
background-color black
font-size 0
.csc-call-media-icon
opacity 0.5
.csc-call-content-minimized
position absolute
bottom 0
left 0
right 0
height $call-footer-height
background-color $call-minimized-background
z-index 3
display block
flex-wrap no-wrap
align-items center
justify-content center
.csc-call-info-minimized
display flex
flex-wrap no-wrap
align-items center
margin-bottom -16px
.csc-call-info-text
color white
padding-left $flex-gutter-xs
.csc-call-info-phrase
margin-bottom 4px
.csc-call-info-number
font-size 14px
.csc-call-info-loading
margin-right $flex-gutter-sm
.csc-call-btn-fullscreen
position absolute
right $flex-gutter-md
top 50%
bottom 50%
margin-top -27px
.csc-call-btn-fullscreen-small
position absolute
right $flex-gutter-sm
top 50%
bottom 50%
margin-top -20px
.csc-call-actions
position absolute
left 0
right 0
top $call-footer-action-margin * -1
.q-btn
.q-btn-inner
color $dark
.q-btn.q-btn-round
box-shadow none
.csc-call-button
margin-right $flex-gutter-sm
.csc-call-button:last-child
margin-right 0
.csc-phone-number
color white
text-align center
.csc-call.csc-call-input
top auto
height $call-footer-height
.csc-call-content
bottom 0
height 0
visibility hidden
.csc-call-media-remote
bottom 0
height 0
visibility hidden
.csc-call.csc-call-established
.csc-call-content
background-color transparent
.csc-call.csc-call-minimized
opacity 0
height $call-footer-height-big
top auto
bottom ($call-footer-height-big + $call-footer-action-margin) * -1
.csc-call-content
bottom 0
height 0
visibility hidden
.csc-call-media-remote
top auto
bottom $call-footer-height-big + $flex-gutter-sm
right $flex-gutter-sm
left auto
height auto
width 20%
z-index 1
font-size 0
.csc-call-content-minimized
height $call-footer-height-big
.csc-call-actions
top $call-footer-action-margin * -1
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing
<style lang="sass" rel="stylesheet/sass">
.csc-call
left: $layout-aside-left-width
top: 0
position: fixed
bottom: 0
right: 0
z-index: 40
.q-btn.q-btn-round
box-shadow: none
.csc-call-content
position: absolute
bottom: $call-footer-height
top: 0
right: 0
left: 0
z-index: 2
background-color: $secondary
.csc-call-info
.csc-call-info-content
margin-top: -80px
.csc-call-error
color: $negative
.csc-call-spinner
margin-bottom: $flex-gutter-sm
.csc-call-media-local
position: absolute
bottom: $flex-gutter-sm
left: $flex-gutter-sm
width: 20%
height: auto
overflow: hidden
z-index: 2
font-size: 0
.csc-call-info-established
position: absolute
bottom: $call-footer-action-margin * 2
right: 0
left: 0
justify-items: center
z-index: 3
color: white
.csc-media-icon
margin-right: $flex-gutter-xs
.csc-dialpad-button
vertical-align: center
margin-left: $flex-gutter-sm
.csc-call-media-remote
position: absolute
top: 0
bottom: $call-footer-height
right: 0
left: 0
z-index: 1
background-color: black
font-size: 0
.csc-call-media-icon
opacity: 0.5
.csc-call-content-minimized
position: absolute
bottom: 0
left: 0
right: 0
height: $call-footer-height
background-color: $call-minimized-background
z-index: 3
display: block
flex-wrap: no-wrap
align-items: center
justify-content: center
.csc-call-info-minimized
display: flex
flex-wrap: no-wrap
align-items: center
margin-bottom: -16px
.csc-call-info-text
color: white
padding-left: $flex-gutter-xs
.csc-call-info-phrase
margin-bottom: 4px
.csc-call-info-number
font-size: 14px
.csc-call-info-loading
margin-right: $flex-gutter-sm
.csc-call-btn-fullscreen
position: absolute
right: $flex-gutter-md
top: 50%
bottom: 50%
margin-top: -27px
.csc-call-btn-fullscreen-small
position: absolute
right: $flex-gutter-sm
top: 50%
bottom: 50%
margin-top: -20px
.csc-call-actions
position: absolute
left: 0
right: 0
top: $call-footer-action-margin * -1
.q-btn
.q-btn-inner
color: $dark
.q-btn.q-btn-round
box-shadow: none
.csc-call-button
margin-right: $flex-gutter-sm
.csc-call-button:last-child
margin-right: 0
.csc-phone-number
color: white
text-align: center
.csc-call.csc-call-input
top: auto
height: $call-footer-height
.csc-call-content
bottom: 0
height: 0
visibility: hidden
.csc-call-media-remote
bottom: 0
height: 0
visibility: hidden
.csc-call.csc-call-established
.csc-call-content
background-color: transparent
.csc-call.csc-call-minimized
opacity: 0
height: $call-footer-height-big
top: auto
bottom: ($call-footer-height-big + $call-footer-action-margin) * -1
.csc-call-content
bottom: 0
height: 0
visibility: hidden
.csc-call-media-remote
top: auto
bottom: $call-footer-height-big + $flex-gutter-sm
right: $flex-gutter-sm
left: auto
height: auto
width: 20%
z-index: 1
font-size: 0
.csc-call-content-minimized
height: $call-footer-height-big
.csc-call-actions
top: $call-footer-action-margin * -1
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing
.csc-call-actions
margin-bottom: 8px
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing,
.csc-call.csc-call-minimized.csc-call-established,
.csc-call.csc-call-minimized.csc-call-ended
bottom: 0
opacity: 1
.csc-call.csc-call-full-width
left: 0
.csc-call.csc-main-menu-minimized
left: $main-menu-minimized-width
.csc-call.csc-call-mobile
.csc-call-content
.csc-call-media-local
font-size: 0
top: $flex-gutter-sm
left: $flex-gutter-sm
bottom: auto
width: 30%
height: auto
overflow: hidden
.csc-call.csc-call-mobile.csc-call-minimized
.csc-call-content-minimized
height: $call-footer-height-big
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-ended,
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-established
.csc-call-content-minimized
height: $call-footer-height * 1.4
justify-content: left
padding-left: $flex-gutter-sm
.csc-call-info-minimized
margin-bottom: 0
.csc-call-actions
margin-bottom 8px
.csc-call.csc-call-minimized.csc-call-incoming,
.csc-call.csc-call-minimized.csc-call-initiating,
.csc-call.csc-call-minimized.csc-call-ringing,
.csc-call.csc-call-minimized.csc-call-established,
.csc-call.csc-call-minimized.csc-call-ended
bottom 0
opacity 1
.csc-call.csc-call-full-width
left 0
.csc-call.csc-main-menu-minimized
left $main-menu-minimized-width
.csc-call.csc-call-mobile
.csc-call-content
.csc-call-media-local
font-size 0
top $flex-gutter-sm
left $flex-gutter-sm
bottom auto
width 30%
height auto
overflow hidden
.csc-call.csc-call-mobile.csc-call-minimized
.csc-call-content-minimized
height $call-footer-height-big
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-ended,
.csc-call.csc-call-mobile.csc-call-minimized.csc-call-established
.csc-call-content-minimized
height $call-footer-height * 1.4
justify-content left
padding-left $flex-gutter-sm
.csc-call-info-minimized
margin-bottom 0
.csc-call-actions
position absolute
margin auto
margin-top -27px
top 50%
right $flex-gutter-sm
left auto
.csc-call-button
margin-right $flex-gutter-sm
.csc-call-button:last-child
margin-right 0
position: absolute
margin: auto
margin-top: -27px
top: 50%
right: $flex-gutter-sm
left: auto
.csc-call-button
margin-right: $flex-gutter-sm
.csc-call-button:last-child
margin-right: 0
</style>

@ -6,24 +6,25 @@
:label="$t('Phone number')"
:value="value"
:disable="!enabled"
:error="$v.phoneNumber.$error"
:error="v$.phoneNumber.$errors.length > 0"
:readonly="readonly"
@keypress.space.prevent
@keydown.space.prevent
@keyup.space.prevent
@keyup.enter="keyReturn()"
@input="inputNumber"
@update:model-value="inputNumber"
/>
</template>
<script>
import Vue from 'vue'
import { nextTick } from 'vue'
import {
userInfoAndEmpty
} from 'src/helpers/validation'
import {
maxLength
} from 'vuelidate/lib/validators'
} from '@vuelidate/validators'
import platformMixin from '../../mixins/platform'
import useValidate from '@vuelidate/core'
export default {
name: 'CscPhoneNumberInput',
validations: {
@ -57,23 +58,26 @@ export default {
default: ''
}
},
emits: ['key-return', 'number-changed'],
data () {
return {
phoneNumber: this.value
phoneNumber: this.value,
v$: useValidate()
}
},
computed: {
errorMessage () {
if (!this.$v.phoneNumber.required) {
const errorsTab = this.v$.phoneNumber.$errors
if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'required') {
return this.$t('{field} is required', {
field: this.$t('Phone number')
})
} else if (!this.$v.phoneNumber.maxLength) {
} else if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'maxLength') {
return this.$t('{field} must have at most {maxLength} letters', {
field: this.$t('Phone number'),
maxLength: this.$v.phoneNumber.$params.maxLength.max
maxLength: this.v$.phoneNumber.maxLength.$params.max
})
} else if (!this.$v.phoneNumber.userInfo) {
} else if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'userInfo') {
return this.$t('Input a valid phone number')
} else {
return ''
@ -86,27 +90,27 @@ export default {
watch: {
value () {
this.phoneNumber = this.value
this.$v.phoneNumber.$touch()
this.v$.phoneNumber.$touch()
}
},
methods: {
inputNumber (input) {
this.phoneNumber = input
this.$v.phoneNumber.$touch()
this.v$.phoneNumber.$touch()
this.$emit('number-changed', this.phoneNumber)
},
keyReturn () {
this.$emit('key-return')
},
focus () {
Vue.nextTick(() => {
nextTick(() => {
if (this.$refs.inputField) {
this.$refs.inputField.focus()
}
})
},
blur () {
Vue.nextTick(() => {
nextTick(() => {
if (this.$refs.inputField) {
this.$refs.inputField.blur()
}
@ -115,5 +119,5 @@ export default {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -2,11 +2,9 @@ s
<template>
<q-input
v-bind="$attrs"
:value="value"
:error="$v.inputValue.$error"
:error="v$.inputValue.$errors.length > 0"
:error-message="errorMessage"
v-on="$listeners"
@input="$emit('input', $event)"
@update:model-value="$emit('input', $event)"
/>
</template>
@ -17,20 +15,17 @@ import {
import {
maxLength,
required
} from 'vuelidate/lib/validators'
} from '@vuelidate/validators'
import useValidate from '@vuelidate/core'
export default {
name: 'CscCallInput',
props: {
value: {
type: String,
required: true
}
},
emits: ['submit', 'input', 'error'],
data () {
return {
inputValue: '',
error: ''
error: '',
v$: useValidate()
}
},
validations: {
@ -42,16 +37,17 @@ export default {
},
computed: {
errorMessage () {
if (!this.$v.inputValue.required) {
const errorsTab = this.v$.inputValue.$errors
if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'required') {
return this.$t('{field} is required', {
field: this.label
})
} else if (!this.$v.inputValue.maxLength) {
} else if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'maxLength') {
return this.$t('{field} must have at most {maxLength} letters', {
field: this.label,
maxLength: this.$v.inputValue.$params.maxLength.max
maxLength: this.v$.inputValue.maxLength.$params.max
})
} else if (!this.$v.inputValue.userInfo) {
} else if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'userInfo') {
return this.$t('Input a valid phone number')
} else {
return ''
@ -71,17 +67,17 @@ export default {
this.$emit('submit')
},
input () {
this.$v.inputValue.$touch()
this.error = this.$v.inputValue.$error
this.v$.inputValue.$touch()
this.error = this.v$.inputValue.$error
this.$emit('input', this.inputValue)
},
blur () {
this.$v.inputValue.$touch()
this.error = this.$v.inputValue.$error
this.v$.inputValue.$touch()
this.error = this.v$.inputValue.$error
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -10,11 +10,11 @@
hide-bottom-space
:label="$t('Password')"
:disable="loading"
:error="$v.password.$error"
:error="v$.password.$errors.length > 0"
:error-message="errorMessagePass"
@blur="$v.password.$touch()"
@blur="v$.password.$touch()"
/>
<password-strength-meter
<password-meter
v-model="passwordScored"
class="full-width"
style="max-width: none;"
@ -29,24 +29,25 @@
hide-bottom-space
:label="$t('Password Retype')"
:disable="loading"
:error="$v.passwordRetype.$error"
:error="v$.passwordRetype.$errors.length > 0"
:error-message="errorMessagePassRetype"
@blur="$v.passwordRetype.$touch();onRetypeBlur()"
@blur="v$.passwordRetype.$touch();onRetypeBlur()"
/>
</div>
</template>
<script>
import PasswordStrengthMeter from 'vue-password-strength-meter'
import PasswordMeter from 'vue-simple-password-meter'
import {
required
} from 'vuelidate/lib/validators'
} from '@vuelidate/validators'
import CscInputPassword from 'components/form/CscInputPassword'
import useValidate from '@vuelidate/core'
export default {
name: 'CscChangePasswordForm',
components: {
CscInputPassword,
PasswordStrengthMeter
PasswordMeter
},
props: {
noSubmit: {
@ -58,12 +59,14 @@ export default {
default: false
}
},
emits: ['validation-succeeded', 'validation-failed'],
data () {
return {
password: '',
passwordRetype: '',
passwordScored: '',
passwordStrengthScore: null
passwordStrengthScore: null,
v$: useValidate()
}
},
validations: {
@ -82,14 +85,16 @@ export default {
},
computed: {
errorMessagePass () {
if (!this.$v.password.passwordStrength) {
const errorsTab = this.v$.password.$errors
if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'passwordStrength') {
return this.$t('Password is not strong enough')
} else {
return ''
}
},
errorMessagePassRetype () {
if (!this.$v.passwordRetype.sameAsPassword) {
const errorsTab = this.v$.passwordRetype.$errors
if (errorsTab && errorsTab.length > 0 && errorsTab[0].$validator === 'sameAsPassword') {
return this.$t('Passwords must be equal')
} else {
return ''
@ -106,17 +111,17 @@ export default {
}
},
methods: {
strengthMeterScoreUpdate (score) {
this.passwordStrengthScore = score
strengthMeterScoreUpdate (evt) {
this.passwordStrengthScore = evt.score
},
resetForm () {
this.password = this.passwordRetype = this.passwordScored = ''
this.passwordStrengthScore = null
this.$v.$reset()
this.v$.$reset()
},
submit () {
this.$v.$touch()
if (this.$v.$invalid) {
this.v$.$touch()
if (this.v$.$invalid) {
this.$emit('validation-failed')
} else {
this.$emit('validation-succeeded', {
@ -126,7 +131,7 @@ export default {
}
},
onRetypeBlur () {
if (this.noSubmit && !this.$v.$invalid) {
if (this.noSubmit && !this.v$.$invalid) {
this.$emit('validation-succeeded', {
password: this.password,
strengthScore: this.passwordStrengthScore
@ -136,3 +141,14 @@ export default {
}
}
</script>
<style lang="sass" rel="stylesheet/sass">
.po-password-strength-bar
border-radius: 2px
transition: all 0.2s linear
height: 6px
margin-bottom: 12px
margin-top: 3px
background-color: #ddd
max-width: none
</style>

@ -12,6 +12,7 @@
<script>
export default {
name: 'CscFormResetButton',
emits: ['click'],
data () {
return {}
},
@ -23,5 +24,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -12,6 +12,7 @@
<script>
export default {
name: 'CscFormSaveButton',
emits: ['click'],
data () {
return {}
},
@ -23,5 +24,5 @@ export default {
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
<style lang="sass" rel="stylesheet/sass">
</style>

@ -1,28 +1,27 @@
<template>
<q-input
:value="value"
:model-value="value"
:clearable="false"
v-bind="$attrs"
@input="$emit('input', $event)"
v-on="$listeners"
@update:model-value="$emit('input', $event)"
>
<template
v-for="(_, slot) of $scopedSlots"
v-slot:[slot]="scope"
v-for="(_, slot) of $slots"
#[slot]="scope"
>
<slot
v-if="slot !== 'loading' && slot !== 'append'"
v-if="slot !== 'loading'"
:name="slot"
v-bind="scope"
/>
</template>
<template
v-slot:loading
#loading
>
<csc-spinner />
</template>
<template
v-slot:append
#append
>
<slot
name="append"
@ -55,6 +54,7 @@ export default {
default: undefined
}
},
emits: ['input', 'clear'],
date () {
return {

@ -7,7 +7,6 @@
:label="$t('Reset')"
:disable="$attrs.loading"
v-bind="$attrs"
v-on="$listeners"
/>
</template>

@ -7,7 +7,6 @@
:label="$t('Save')"
:disable="$attrs.loading"
v-bind="$attrs"
v-on="$listeners"
/>
</template>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save