Merge pull request #21 from shariquerik/twilio-fix
This commit is contained in:
commit
a8c32cff36
@ -7,6 +7,7 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"section_break_malx",
|
||||||
"account_sid",
|
"account_sid",
|
||||||
"api_key",
|
"api_key",
|
||||||
"api_secret",
|
"api_secret",
|
||||||
@ -14,8 +15,9 @@
|
|||||||
"auth_token",
|
"auth_token",
|
||||||
"twiml_sid",
|
"twiml_sid",
|
||||||
"section_break_ssqj",
|
"section_break_ssqj",
|
||||||
"record_calls",
|
"enabled",
|
||||||
"column_break_avmt"
|
"column_break_avmt",
|
||||||
|
"record_calls"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -23,7 +25,7 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Account SID",
|
"label": "Account SID",
|
||||||
"reqd": 1
|
"mandatory_depends_on": "eval: doc.enabled"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "api_key",
|
"fieldname": "api_key",
|
||||||
@ -46,7 +48,7 @@
|
|||||||
"fieldtype": "Password",
|
"fieldtype": "Password",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Auth Token",
|
"label": "Auth Token",
|
||||||
"reqd": 1
|
"mandatory_depends_on": "eval: doc.enabled"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "twiml_sid",
|
"fieldname": "twiml_sid",
|
||||||
@ -67,12 +69,22 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_avmt",
|
"fieldname": "column_break_avmt",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_malx",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enabled"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-20 16:42:17.025651",
|
"modified": "2023-11-03 15:13:09.155818",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "FCRM",
|
"module": "FCRM",
|
||||||
"name": "Twilio Settings",
|
"name": "Twilio Settings",
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from .twilio_handler import Twilio, IncomingCall, TwilioCallDetails
|
from .twilio_handler import Twilio, IncomingCall, TwilioCallDetails
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def is_enabled():
|
||||||
|
return frappe.db.get_single_value("Twilio Settings", "enabled")
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def generate_access_token():
|
def generate_access_token():
|
||||||
"""Returns access token that is required to authenticate Twilio Client SDK.
|
"""Returns access token that is required to authenticate Twilio Client SDK.
|
||||||
|
|||||||
@ -27,8 +27,8 @@ class Twilio:
|
|||||||
"""Make a twilio connection.
|
"""Make a twilio connection.
|
||||||
"""
|
"""
|
||||||
settings = frappe.get_doc("Twilio Settings")
|
settings = frappe.get_doc("Twilio Settings")
|
||||||
# if not (settings and settings.enabled):
|
if not (settings and settings.enabled):
|
||||||
# return
|
return
|
||||||
return Twilio(settings=settings)
|
return Twilio(settings=settings)
|
||||||
|
|
||||||
def get_phone_numbers(self):
|
def get_phone_numbers(self):
|
||||||
@ -115,6 +115,8 @@ class Twilio:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_twilio_client(self):
|
def get_twilio_client(self):
|
||||||
twilio_settings = frappe.get_doc("Twilio Settings")
|
twilio_settings = frappe.get_doc("Twilio Settings")
|
||||||
|
if not twilio_settings.enabled:
|
||||||
|
frappe.throw(_("Please enable twilio settings before making a call."))
|
||||||
|
|
||||||
auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token')
|
auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token')
|
||||||
client = TwilioClient(twilio_settings.account_sid, auth_token)
|
client = TwilioClient(twilio_settings.account_sid, auth_token)
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 38eb500cb47d6cfc7124817e9c2acdcd560045f1
|
Subproject commit e0859c6165a5d54a999f94e7c1cf9647ecd2bf72
|
||||||
@ -9,21 +9,23 @@
|
|||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tiptap/vue-3": "^2.0.4",
|
|
||||||
"@twilio/voice-sdk": "^2.7.1",
|
"@twilio/voice-sdk": "^2.7.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
|
||||||
"@vueuse/core": "^10.3.0",
|
"@vueuse/core": "^10.3.0",
|
||||||
"@vueuse/integrations": "^10.3.0",
|
"@vueuse/integrations": "^10.3.0",
|
||||||
"autoprefixer": "^10.4.14",
|
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.12",
|
"frappe-ui": "^0.1.14",
|
||||||
"pinia": "^2.0.33",
|
"pinia": "^2.0.33",
|
||||||
"postcss": "^8.4.5",
|
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.2"
|
"vue-router": "^4.2.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
|
"postcss": "^8.4.5",
|
||||||
|
"vite": "^4.4.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
<div
|
<div
|
||||||
v-show="showCallPopup"
|
v-show="showCallPopup"
|
||||||
ref="callPopup"
|
ref="callPopup"
|
||||||
class="fixed select-none z-10 bg-gray-900 text-gray-300 rounded-lg shadow-2xl p-4 flex flex-col w-60 cursor-move"
|
class="fixed z-10 flex w-60 cursor-move select-none flex-col rounded-lg bg-gray-900 p-4 text-gray-300 shadow-2xl"
|
||||||
:style="style"
|
:style="style"
|
||||||
>
|
>
|
||||||
<div class="flex items-center flex-row-reverse gap-1">
|
<div class="flex flex-row-reverse items-center gap-1">
|
||||||
<MinimizeIcon class="w-4 h-4 cursor-pointer" @click="toggleCallWindow" />
|
<MinimizeIcon class="h-4 w-4 cursor-pointer" @click="toggleCallWindow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center items-center gap-3">
|
<div class="flex flex-col items-center justify-center gap-3">
|
||||||
<Avatar
|
<Avatar
|
||||||
:image="contact.image"
|
:image="contact.image"
|
||||||
:label="contact.full_name"
|
:label="contact.full_name"
|
||||||
class="flex items-center justify-center [&>div]:text-[30px] !h-24 !w-24 relative"
|
class="relative flex !h-24 !w-24 items-center justify-center [&>div]:text-[30px]"
|
||||||
:class="onCall || calling ? '' : 'pulse'"
|
:class="onCall || calling ? '' : 'pulse'"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col items-center justify-center gap-1">
|
<div class="flex flex-col items-center justify-center gap-1">
|
||||||
@ -22,11 +22,11 @@
|
|||||||
<div class="text-sm text-gray-600">{{ contact.mobile_no }}</div>
|
<div class="text-sm text-gray-600">{{ contact.mobile_no }}</div>
|
||||||
</div>
|
</div>
|
||||||
<CountUpTimer ref="counterUp">
|
<CountUpTimer ref="counterUp">
|
||||||
<div v-if="onCall" class="text-base my-1">
|
<div v-if="onCall" class="my-1 text-base">
|
||||||
{{ counterUp?.updatedTime }}
|
{{ counterUp?.updatedTime }}
|
||||||
</div>
|
</div>
|
||||||
</CountUpTimer>
|
</CountUpTimer>
|
||||||
<div v-if="!onCall" class="text-base my-1">
|
<div v-if="!onCall" class="my-1 text-base">
|
||||||
{{
|
{{
|
||||||
callStatus == 'ringing'
|
callStatus == 'ringing'
|
||||||
? 'Ringing...'
|
? 'Ringing...'
|
||||||
@ -43,13 +43,13 @@
|
|||||||
/>
|
/>
|
||||||
<Button class="rounded-full">
|
<Button class="rounded-full">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DialpadIcon class="rounded-full cursor-pointer" />
|
<DialpadIcon class="cursor-pointer rounded-full" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="rounded-full">
|
<Button class="rounded-full">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NoteIcon
|
<NoteIcon
|
||||||
class="text-gray-900 rounded-full cursor-pointer h-4 w-4"
|
class="h-4 w-4 cursor-pointer rounded-full text-gray-900"
|
||||||
@click="showNoteModal = true"
|
@click="showNoteModal = true"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<Button class="rounded-full bg-red-600 hover:bg-red-700">
|
<Button class="rounded-full bg-red-600 hover:bg-red-700">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PhoneIcon
|
<PhoneIcon
|
||||||
class="text-white fill-white h-4 w-4 rotate-[135deg]"
|
class="h-4 w-4 rotate-[135deg] fill-white text-white"
|
||||||
@click="hangUpCall"
|
@click="hangUpCall"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -73,7 +73,7 @@
|
|||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<PhoneIcon class="fill-white h-4 w-4 rotate-[135deg]" />
|
<PhoneIcon class="h-4 w-4 rotate-[135deg] fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
@click="acceptIncomingCall"
|
@click="acceptIncomingCall"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<PhoneIcon class="fill-white h-4 w-4" />
|
<PhoneIcon class="h-4 w-4 fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -99,7 +99,7 @@
|
|||||||
@click="rejectIncomingCall"
|
@click="rejectIncomingCall"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<PhoneIcon class="fill-white h-4 w-4 rotate-[135deg]" />
|
<PhoneIcon class="h-4 w-4 rotate-[135deg] fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -107,16 +107,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="showSmallCallWindow"
|
v-show="showSmallCallWindow"
|
||||||
class="flex items-center justify-between gap-3 bg-gray-900 text-base text-gray-300 ml-2 px-2 py-[7px] rounded-lg cursor-pointer select-none"
|
class="ml-2 flex cursor-pointer select-none items-center justify-between gap-3 rounded-lg bg-gray-900 px-2 py-[7px] text-base text-gray-300"
|
||||||
@click="toggleCallWindow"
|
@click="toggleCallWindow"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Avatar
|
<Avatar
|
||||||
:image="contact.image"
|
:image="contact.image"
|
||||||
:label="contact.full_name"
|
:label="contact.full_name"
|
||||||
class="flex items-center justify-center !h-5 !w-5 relative"
|
class="relative flex !h-5 !w-5 items-center justify-center"
|
||||||
/>
|
/>
|
||||||
<div class="truncate max-w-[120px]">
|
<div class="max-w-[120px] truncate">
|
||||||
{{ contact.full_name }}
|
{{ contact.full_name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -124,10 +124,10 @@
|
|||||||
<div class="my-1 min-w-[40px] text-center">
|
<div class="my-1 min-w-[40px] text-center">
|
||||||
{{ counterUp?.updatedTime }}
|
{{ counterUp?.updatedTime }}
|
||||||
</div>
|
</div>
|
||||||
<Button variant="solid" theme="red" class="rounded-full !h-6 !w-6">
|
<Button variant="solid" theme="red" class="!h-6 !w-6 rounded-full">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PhoneIcon
|
<PhoneIcon
|
||||||
class="fill-white h-4 w-4 rotate-[135deg]"
|
class="h-4 w-4 rotate-[135deg] fill-white"
|
||||||
@click.stop="hangUpCall"
|
@click.stop="hangUpCall"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -140,11 +140,11 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
theme="red"
|
theme="red"
|
||||||
class="rounded-full !h-6 !w-6"
|
class="!h-6 !w-6 rounded-full"
|
||||||
@click.stop="cancelCall"
|
@click.stop="cancelCall"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PhoneIcon class="fill-white h-4 w-4 rotate-[135deg]" />
|
<PhoneIcon class="h-4 w-4 rotate-[135deg] fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -152,21 +152,21 @@
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
theme="green"
|
theme="green"
|
||||||
class="rounded-full !h-6 !w-6 pulse relative"
|
class="pulse relative !h-6 !w-6 rounded-full"
|
||||||
@click.stop="acceptIncomingCall"
|
@click.stop="acceptIncomingCall"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PhoneIcon class="fill-white h-4 w-4 animate-pulse" />
|
<PhoneIcon class="h-4 w-4 animate-pulse fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
theme="red"
|
theme="red"
|
||||||
class="rounded-full !h-6 !w-6"
|
class="!h-6 !w-6 rounded-full"
|
||||||
@click.stop="rejectIncomingCall"
|
@click.stop="rejectIncomingCall"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PhoneIcon class="fill-white h-4 w-4 rotate-[135deg]" />
|
<PhoneIcon class="h-4 w-4 rotate-[135deg] fill-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -197,6 +197,7 @@ const contact = ref({
|
|||||||
mobile_no: '',
|
mobile_no: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let enabled = ref(false)
|
||||||
let showCallPopup = ref(false)
|
let showCallPopup = ref(false)
|
||||||
let showSmallCallWindow = ref(false)
|
let showSmallCallWindow = ref(false)
|
||||||
let onCall = ref(false)
|
let onCall = ref(false)
|
||||||
@ -244,6 +245,10 @@ let { style } = useDraggable(callPopup, {
|
|||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function is_twilio_enabled() {
|
||||||
|
return await call('crm.twilio.api.is_enabled')
|
||||||
|
}
|
||||||
|
|
||||||
async function startupClient() {
|
async function startupClient() {
|
||||||
log.value = 'Requesting Access Token...'
|
log.value = 'Requesting Access Token...'
|
||||||
|
|
||||||
@ -469,7 +474,10 @@ function toggleCallWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => startupClient())
|
onMounted(async () => {
|
||||||
|
enabled.value = await is_twilio_enabled()
|
||||||
|
enabled.value && startupClient()
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => log.value,
|
() => log.value,
|
||||||
@ -481,6 +489,7 @@ watch(
|
|||||||
|
|
||||||
const app = getCurrentInstance()
|
const app = getCurrentInstance()
|
||||||
app.appContext.config.globalProperties.makeCall = makeOutgoingCall
|
app.appContext.config.globalProperties.makeCall = makeOutgoingCall
|
||||||
|
app.appContext.config.globalProperties.is_twilio_enabled = enabled.value
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
6
frontend/src/composables/twilio.js
Normal file
6
frontend/src/composables/twilio.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { getCurrentInstance } from 'vue'
|
||||||
|
|
||||||
|
export function is_twilio_enabled() {
|
||||||
|
const app = getCurrentInstance()
|
||||||
|
return app.appContext.config.globalProperties.is_twilio_enabled
|
||||||
|
}
|
||||||
@ -5,23 +5,34 @@ import frappeui from 'frappe-ui/vite'
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [frappeui(), vue({
|
plugins: [
|
||||||
script: {
|
frappeui(),
|
||||||
defineModel: true,
|
vue({
|
||||||
propsDestructure: true
|
script: {
|
||||||
}
|
defineModel: true,
|
||||||
})],
|
propsDestructure: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: `../${path.basename(path.resolve('..'))}/public/frontend`,
|
outDir: '../crm/public/frontend',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
include: [/tailwind.config.js/, /node_modules/],
|
||||||
|
},
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['frappe-ui > feather-icons', 'showdown', 'engine.io-client'],
|
include: [
|
||||||
|
'feather-icons',
|
||||||
|
'showdown',
|
||||||
|
'tailwind.config.js',
|
||||||
|
'engine.io-client',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
1795
frontend/yarn.lock
1795
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"aworkspaces": ["frappe-ui", "frontend"],
|
"workspaces": ["frontend", "frappe-ui"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "cd frontend && yarn install",
|
"postinstall": "cd frontend && yarn install",
|
||||||
"dev": "cd frontend && yarn dev",
|
"dev": "cd frontend && yarn dev",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user