<template>
<LayoutPage>
<PageTitle :title-page="$t('catalog.title')" />
<Alert
v-if="errorResponse"
type="danger"
:is-dismissible="true"
:text="responseMsg"
@handle-close="errorResponse = false"
style="margin:-20px 0 -40px 0; z-index:1;"
/>
<div v-if="generalLoading" class="d-flex justify-content-center align-items-center vh-100">
<SpinnerLoading />
</div>
<div v-else :class="['card p-3', { 'block-section': errorResponse }]">
<div class="card-body">
<div class="row mb-3">
<div class="col-12 bg-soft-primary rounded py-3 px-4 mb-5">
<template v-if="showCVComponent">
<template v-if="isVerifiedNumberSuccess">
<div class="container pt-3 pb-5">
<div class="row justify-content-center">
<div class="col-12 col-md-6 text-center">
<i class="fas fa-check fa-3x text-success"></i>
<p class="fw-bold mt-4">
{{ $t('verifiedNumber.verifiedTitle', { value: verifiedResponse.phoneNumber }) }}
</p>
<p class="text-muted">
{{ $t('verifiedNumber.verifiedMsg') }}
</p>
<p>
<button
type="button"
class="btn btn-deep px-3"
@click="initCFComponent"
>
{{ $t('buttons.accept')}}
</button>
</p>
</div>
</div>
</div>
</template>
<template v-else-if="isVerifiedNumberError">
<div class="container pt-3 pb-5">
<div class="row justify-content-center">
<div class="col-12 col-md-6 text-center">
<i class="far fa-times-circle fa-3x text-danger"></i>
<p class="fw-bold mt-4">
{{ $t('verifiedNumber.notVerifiedTitle', { value: verifiedResponse.phoneNumber }) }}
</p>
<p class="text-muted" v-html="$t('verifiedNumber.notVerifiedMsg', { value: 'support@flynode.mx' })"></p>
<p>
<button
type="button"
class="btn btn-deep px-3"
@click="initCFComponent"
>
{{ $t('buttons.accept')}}
</button>
</p>
</div>
</div>
</div>
</template>
<template v-else>
<CodeVerification
:statusResponse="verifiedResponse"
@onAlert="triggerModal"
:label="verifiedNumberLabels"
:submitCode="verifyCode"
v-model="verificationCode"
:sendNew="sendNewCode"
/>
</template>
</template>
<template v-else>
<h5 class="mb-3">{{$t('verifiedNumber.verifyNewNumber')}}</h5>
<div class="row gx-5">
<div class="col-md-3 mb-5">
<Multiselect
v-model="verifyModel.country_code_id"
:placeholder="$t('selectOption')"
track-by="country_name"
valueProp="country_code"
label="country_name"
:options="countryCodes"
@open="initGetCountryCodes"
@change="handleChange"
:searchable="true"
:noOptionsText="$t('loading')"
:class="['multiselect-custom', { 'border-danger': errors.country_code_id }]"
>
<template v-slot:singlelabel="{ value }">
<div class="multiselect-single-label">
<img class="option-image" :src="`${urlAssets}/${value.path_to_flag}`" width="40" height="24" style="width:40px; height:24px;"> ({{value.country_code}}) {{ value.country_name }}
</div>
</template>
<template v-slot:option="{ option }">
<img class="option-image" :src="`${urlAssets}/${option.path_to_flag}`" width="40" height="24" style="width:40px; height:24px;">({{option.country_code}} ) {{ option.country_name }}
</template>
</Multiselect>
<div v-if="errors.country_code_id" class="invalid-feedback d-block">{{errors.country_code_id}}</div>
</div>
<div v-if="togglePhoneType" class="col-md-4 mb-5">
<select :class="['form-select bg-white', { 'is-invalid': errors.type }]" v-model="verifyModel.type">
<option value="null" disabled>{{$t('selectOption')}}</option>
<option value="mobile">{{$t('verifiedNumber.mobile')}}</option>
<option value="permanent">{{$t('verifiedNumber.permanent')}}</option>
</select>
<div v-if="errors.type" class="invalid-feedback">{{errors.type}}</div>
</div>
<div class="col-md-4 mb-5">
<input type="text" :class="['form-control bg-white', { 'is-invalid': errors.phone_number }]" :placeholder="$t('verifiedNumber.addPhoneNumber')" v-model="verifyModel.phone_number"/>
<div v-if="errors.phone_number" class="invalid-feedback">{{errors.phone_number}}</div>
</div>
</div>
<div class="text-center">
<button type="button" class="btn btn-orange text-white fw-bold"
@click="submitVerificationCode"
:disabled="isSubmitLoading"
>
<template v-if="isSubmitLoading">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="visually-hidden">Loading...</span>
</template>
{{$t('verifiedNumber.sendVerificationCode')}}</button>
<div class="d-inline ms-2">
<CustomTooltip
placement="right"
data-bs-html="true"
:title="`<div>${$t('verifiedNumber.sendTooltip')}</div>`"
/>
</div>
</div>
</template>
</div>
<div class="col-md-9">
<h5 class="fw-bold">{{$t('verifiedNumber.title')}}</h5>
</div>
<div class="col-md-3 text-end d-none">
<router-link
class="btn btn-orange text-white fw-bold px-4"
:to="{name: 'NewVerifiedNumbers'}"
>
<i class="fas fa-plus fa-sm"></i>
{{$t('verifiedNumber.newOption')}}
</router-link>
</div>
</div>
<DataTable
:url="NUMBERS_URL"
:columns="tableColumnsName"
:search-input="true"
@emitData="emitTableData"
>
<tbody>
<tr v-if="data.length === 0">
{{$t('table.noResultsFound')}}
</tr>
<tr v-else v-for="(item, key) in data" :key="key" class="text-center">
<td>{{ item.id }}</td>
<td>{{ item.number }}</td>
<td>{{ item.type }}</td>
<td>{{ item.country_name }}</td>
<td>
<CBadge :colorClass="statusClass(item.status_code)">
{{$t(`status.${item.status}`)}}
</CBadge>
</td>
<td class="d-flexjustify-content-center">
<template v-if="item.block_edit">
<span class="btn btn-primary btn-sm mx-1" style="cursor:default;">
<i class="fas fa-lock"></i>
</span>
</template>
<template v-else>
<router-link class="btn btn-primary btn-sm mx-1 d-none" :to="{name: 'EditVerifiedNumbers', params: {id: item.id} }">
<i class="fas fa-pen"></i>
</router-link>
<button class="btn btn-primary btn-sm mx-1" @click="deleteRow(item)">
<i class="fas fa-trash"></i>
</button>
</template>
</td>
</tr>
</tbody>
</DataTable>
</div>
</div>
</LayoutPage>
</template>
<script setup>
import LayoutPage from '@/components/LayoutPage.vue';
import PageTitle from '@/components/PageTitle.vue';
import Alert from '@/components/Alert.vue';
import SingleAccordion from '@/components/SingleAccordion';
import CustomTooltip from "@/components/CustomTooltip.vue";
import flatPickr from 'vue-flatpickr-component';
import Multiselect from '@vueform/multiselect';
import CodeVerification from '@/components/CodeVerification.vue';
import CBadge from '@/components/CBadge.vue';
import Swal from 'sweetalert2';
import DataTable from '@/components/organismos/DataTable';
import i18n from "@/i18n";
import { reactive, ref, onMounted } from 'vue';
import {useRouter} from 'vue-router';
import axios from 'axios';
import { throwErrorMessage } from '@/composables/helper';
import {filter} from '@/validators/catalogueSchema.js';
import {getCountryCodes} from '@/composables/sip';
import {verifySchema, verifiedNumbers} from '@/validators/verifyNumberSchema.js';
const generalLoading = ref(false);//temporaly waiting for services
const filterData = ref({});
const filterErrors = ref({});
const data = ref([]);
const deleteSuccess = ref(false);
const selectedStatus = ref({});
const showCVComponent = ref(false);
const isVerifiedNumberSuccess = ref(null),
isVerifiedNumberError = ref(null);
const status = ref([]);
const errorResponse = ref(false);
const responseMsg = ref('');
const errors = ref({});
const urlAssets = process.env.VUE_APP_ASSETS_URL;
const togglePhoneType = ref(false);
const verifiedResponse = reactive({
status:true,
statusName: "sending",
phoneNumber: null
});
const countryCodes = ref([]);
const verifyModel = ref({});
const isSubmitLoading = ref(false);
const router = useRouter();
const NUMBERS_URL = ref(`${process.env.VUE_APP_API_URL}/api/dids/status/`);
const tableColumnsName = ref([
{ label: '#', name: 'id', sort: false },
{ label: i18n.global.t('table.number'), name: 'number', sort: false },
{ label: i18n.global.t('table.type'), name: 'type', sort: true },
{ label: i18n.global.t('country'), name: 'country', sort: true },
{ label: i18n.global.t('table.status'), name: 'status', sort: true },
{ label: i18n.global.t('table.actions'), name: 'actions', sort: false },
]);
const attempts = ref(3);
const verificationCode = ref("");
const verifiedNumberLabels = reactive({
titleLabel : i18n.global.t('verifiedNumber.component.titleLabel'),
instructionLabel: i18n.global.t('verifiedNumber.component.instructionLabel'),
verificationCodeLabel: i18n.global.t('verifiedNumber.component.verificationCodeLabel'),
verifyButtonLabel: i18n.global.t('verifiedNumber.component.verifyButtonLabel'),
cancelButtonLabel: i18n.global.t('verifiedNumber.component.cancelButtonLabel'),
codeNotReceivedLabel: i18n.global.t('verifiedNumber.component.codeNotReceivedLabel'),
clickHereLabel: i18n.global.t('verifiedNumber.component.clickHereLabel'),
codeSentLabel: i18n.global.t('verifiedNumber.component.codeSentLabel'),
sendNewCodeLabel: i18n.global.t('verifiedNumber.component.sendNewCodeLabel'),
attemptsLabel: i18n.global.t('verifiedNumber.component.attemptsLabel', { attempts: attempts.value})
});
const emitTableData = (source) => data.value = source;
const getStatus = async() => {
try {
const { data: { data: {payload} } } = await axios.get(`${process.env.VUE_APP_API_URL}/api/status/`);
status.value = payload.filter( e => (e.status == 'inactive' || e.status == 'active' || e.status == 'eliminated' ? e : null));
} catch (error) {
console.log(error)
}
}
const statusClass = (val) => {
return val == 1 ? 'bg-success'
: val == 0 ? 'bg-danger'
: 'bg-secondary';
};
const handleChange = (option) => {
if(option && option == 52) {
return togglePhoneType.value = false;
}
if(option && option != 52) return togglePhoneType.value = true;
}
const filterDataTable = async() => {
try {
filterErrors.value = {};
await filter.validate( filterData.value, { abortEarly: false });
DIDSTATUS_URL.value = `${process.env.VUE_APP_API_URL}/api/dids/status/?date_from=${filterData.value.startDate} 00:00:00&date_to=${filterData.value.endDate} 23:59:59&filter_key=status&filter_key_value=${filterData.value.status}`;
} catch (error) {
console.log(error);
if(error?.name == 'ValidationError'){
error.inner.forEach((err) => {
filterErrors.value = { ...filterErrors.value, [err.path]: i18n.global.t(err.message) };
});
}else{
const errorMsg = throwErrorMessage(error);
errorResponse.value = true;
responseMsg.value = errorMsg;
}
}
};
const deleteRow = async(obj) => {
const {id,status_name} = obj;
Swal.fire({
html: `
<i class="fas fa-times-circle fa-3x text-danger mt-4 mb-2"></i>
<p class="text-muted fw-bold mt-3 mb-2">${i18n.global.t('deleteQuestion', { value: status_name })}</p>`,
showCancelButton: true,
cancelButtonText: i18n.global.t('buttons.cancel'),
confirmButtonText: i18n.global.t('buttons.accept'),
showCloseButton: true,
reverseButtons: true,
customClass: {
cancelButton: 'btn btn-soft-secondary fw-bold px-5 mx-3',
confirmButton: 'btn btn-primary fw-bold px-5 mx-3',
htmlContainer: 'container fs-6',
validationMessage: 'bg-soft-danger'
},
buttonsStyling: false,
preConfirm: async() => {
try {
const response = await axios.delete(`${process.env.VUE_APP_API_URL}/api/dids/status/${id}`);
deleteSuccess.value = true;
} catch (error) {
console.log(error);
Swal.showValidationMessage(
`Request failed: ${error}`
)
}
}
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
html: `
<i class="fas fa-check-circle fa-3x text-success mt-4 mb-2"></i>
<p class="text-muted fw-bold mt-3 mb-2">${i18n.global.t('deleteSuccessMsg')}</p>`,
confirmButtonText: i18n.global.t('buttons.exit'),
showCloseButton: true,
customClass: {
confirmButton: 'btn btn-soft-secondary fw-bold px-5 mx-3',
htmlContainer: 'container fs-6',
},
buttonsStyling: false
}).then((result) => {
if (result.isDismissed || result.isConfirmed) {
router.go();
}
})
}
})
}
const triggerModal = () => {
Swal.fire({
iconHtml: '<i class="far fa-times-circle text-danger"></i>',
html: `
<h5 class="text-danger text-center">${i18n.global.t('verifiedNumber.noConnectionTitle')}</h5>
<p class="text-secondary text-start fs-6 mt-3 fst-italic mb-0">${i18n.global.t('verifiedNumber.noConnectionMsg')}</p>`,
showCancelButton: true,
cancelButtonText: i18n.global.t('buttons.cancel'),
confirmButtonText: i18n.global.t('buttons.call'),
showCloseButton: false,
reverseButtons: true,
allowOutsideClick: false,
customClass: {
icon: 'border-0',
cancelButton: 'btn btn-secondary fw-bold w-150px mx-3',
confirmButton: 'btn btn-primary fw-bold w-150px mx-3',
actions: '',
validationMessage: 'bg-soft-danger',
htmlContainer: 'mt-0',
},
buttonsStyling: false,
preConfirm: async() => {
try {
// const response = await axios.put(`${process.env.VUE_APP_API_URL}/api/[endpoint]/${id}`);
// updateSuccess.value = true;
} catch (error) {
console.log(error);
Swal.showValidationMessage(
`Request failed: ${error}`
)
}
}
}).then((result) => {
console.log(result);
if (result.isConfirmed){
console.log("running something behind!")
/* Swal.fire({
html: `
<p class="text-muted fw-bold mt-3 mb-2">success</p>`,
confirmButtonText: 'Exit',
showCloseButton: true,
customClass: {
confirmButton: 'btn btn-secondary fw-bold px-5 mx-3',
actions: 'd-flex justify-content-end me-5 ms-0',
},
buttonsStyling: false
}) */
}else{
console.log(result.isConfirmed, result.dismiss)
isVerifiedNumberError.value = true;
}
})
};
const submitVerificationCode = async() => {
try {
errors.value = {};
isSubmitLoading.value = true;
const savedNumbers = ["2291387761","2291345567"];
await verifySchema.validate( verifyModel.value, { abortEarly: false });
await verifiedNumbers(savedNumbers).validate({'phone_number': verifyModel.value.phone_number}, { abortEarly: false});
//send initial code
testCodeSent(true)
} catch (error) {
isSubmitLoading.value = false;
if(error?.name == 'ValidationError'){
error.inner.forEach((err) => {
errors.value = { ...errors.value, [err.path]: i18n.global.t(err.message) };
});
}else{
const errorMsg = throwErrorMessage(error);
errorResponse.value = true;
responseMsg.value = errorMsg;
}
}
}
const testCodeSent = (val) => {
isSubmitLoading.value = true;
setTimeout(() =>{
isSubmitLoading.value = false;
showCVComponent.value = true;
},1600);
setTimeout(() => {
verifiedResponse.phoneNumber = verifyModel.value.phone_number;
if(val){
verifiedResponse.status = true;
verifiedResponse.statusName = 'sent';
}else{
verifiedResponse.status = false;
verifiedResponse.statusName = 'notSent';
}
}, 15000);
}
const verifyCode = () => {
const code = 3316;
let count = 0;
// compare the verification code, count the failed captured attempts
console.log(`captured code: ${verificationCode.value}, attempts ${attempts.value}`);
if(verificationCode.value != code && attempts.value > 0){
attempts.value = attempts.value - 1;
verifiedNumberLabels.attemptsLabel = i18n.global.t('verifiedNumber.component.attemptsLabel', { attempts: attempts.value});
verifiedResponse.status = true;
verifiedResponse.statusName = 'failed_attempts';
if(attempts.value == 0) {
attempts.value = 3;
isVerifiedNumberError.value = true;
}
}else{
isVerifiedNumberSuccess.value = true;
attempts.value = 3;
verifyModel.value.phoneNumber = "";
}
}
const sendNewCode = () => {
console.log("reset status")
verificationCode.value = "";
verifiedResponse.status = true;
verifiedResponse.statusName = 'sending';
verifiedResponse.phoneNumber = verifyModel.value.phone_number;
setTimeout(() => {
verifiedResponse.statusName = 'sent';
}, 14000);
}
const initCFComponent = () => {
attempts.value = 3;
showCVComponent.value = false;
isVerifiedNumberSuccess.value = false;
isVerifiedNumberError.value = false;
verificationCode.value = "";
verifyModel.value = {};
togglePhoneType.value = false;
}
//const loading = ref(true);
const initGetCountryCodes = async() => {
if(countryCodes.value.length > 0) return;
const { data : { data: {payload} } } = await getCountryCodes();
countryCodes.value = payload;
loading.value = false;
}
getStatus();
/* getCountryCodes()
.then(e => {
countryCodes.value = e.data.data.payload;
}).catch(error => {
console.warn(error)
}); */
</script>
<style src="@vueform/multiselect/themes/default.css"></style>
<style>
.w-150px {
width: 150px!important;
}
</style>
<style scoped>
.multiselect-custom {
--ms-tag-bg: #2A7DDC;
--ms-option-bg-selected-pointed: #2A7DDC;
}
.custom-input-group.with-icon>i {
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
pointer-events: none;
position: absolute;
z-index: 3;
top: 50%;
}
.custom-input-group.with-icon>i,
.custom-input-group.with-icon.icon-right>i{
right: .7rem;
}
.custom-input-group.with-icon>input,
.custom-input-group.with-icon.icon-right>input {
padding-right: 1.7rem;
}
/* .swal2-actions{
justify-content: end !important;
margin: 1.25em 2rem 0px;
} */
.swal2-html-container{
padding-right: 3rem !important;
padding-left: 3rem !important;
}
</style>