From a1a81e37996619798bb44f23ac2f2c0ed67d5f66 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sun, 5 Apr 2020 22:11:27 +0530 Subject: [PATCH] Feature: Twilio SMS Channel (#658) Twilio SMS Channel Fixes : #350 --- Gemfile | 4 +- Gemfile.lock | 33 ++-- app/builders/contact_builder.rb | 37 +++++ .../channels/twilio_channels_controller.rb | 50 ++++++ app/controllers/twilio/callback_controller.rb | 29 ++++ .../dashboard/api/channel/twilioChannel.js | 9 ++ .../assets/images/channels/twilio.png | Bin 0 -> 12215 bytes .../components/layout/SidebarItem.vue | 4 + .../components/widgets/ChannelItem.vue | 6 +- .../dashboard/i18n/locale/en/inboxMgmt.json | 29 ++++ .../dashboard/settings/agents/AddAgent.vue | 10 +- .../dashboard/settings/inbox/ChannelList.vue | 9 +- .../dashboard/settings/inbox/FinishSetup.vue | 2 + .../routes/dashboard/settings/inbox/Index.vue | 3 + .../dashboard/settings/inbox/Settings.vue | 10 +- .../settings/inbox/channel-factory.js | 2 + .../settings/inbox/channels/Twilio.vue | 143 ++++++++++++++++++ .../settings/inbox/channels/Website.vue | 12 +- .../dashboard/store/modules/inboxes.js | 13 ++ app/javascript/shared/mixins/alertMixin.js | 8 + app/javascript/shared/mixins/configMixin.js | 7 + app/models/account.rb | 3 +- app/models/channel/twilio_sms.rb | 33 ++++ app/models/channel/twitter_profile.rb | 4 + app/models/message.rb | 2 + app/models/webhook.rb | 4 + .../twilio/incoming_message_service.rb | 77 ++++++++++ .../twilio/outgoing_message_service.rb | 34 +++++ app/services/twilio/webhook_setup_service.rb | 35 +++++ .../twilio_channels/create.json.jbuilder | 6 + .../v1/accounts/inboxes/index.json.jbuilder | 1 + app/views/layouts/vueapp.html.erb | 1 + config/routes.rb | 8 +- ...0200330071706_create_channel_twilio_sms.rb | 11 ++ ...00404135009_add_unique_validation_index.rb | 7 + db/schema.rb | 14 +- spec/builders/contact_builder_spec.rb | 40 +++++ .../twilio_channels_controller_spec.rb | 76 ++++++++++ .../twilio/callbacks_controller_spec.rb | 18 +++ .../twitter/callbacks_controller_spec.rb | 2 +- spec/factories/channel/twilio_sms.rb | 9 ++ .../twilio/incoming_message_service_spec.rb | 39 +++++ .../twilio/outgoing_message_service_spec.rb | 59 ++++++++ .../twilio/webhook_setup_service_spec.rb | 48 ++++++ 44 files changed, 918 insertions(+), 33 deletions(-) create mode 100644 app/builders/contact_builder.rb create mode 100644 app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb create mode 100644 app/controllers/twilio/callback_controller.rb create mode 100644 app/javascript/dashboard/api/channel/twilioChannel.js create mode 100644 app/javascript/dashboard/assets/images/channels/twilio.png create mode 100644 app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Twilio.vue create mode 100644 app/javascript/shared/mixins/alertMixin.js create mode 100644 app/javascript/shared/mixins/configMixin.js create mode 100644 app/models/channel/twilio_sms.rb create mode 100644 app/services/twilio/incoming_message_service.rb create mode 100644 app/services/twilio/outgoing_message_service.rb create mode 100644 app/services/twilio/webhook_setup_service.rb create mode 100644 app/views/api/v1/accounts/channels/twilio_channels/create.json.jbuilder create mode 100644 db/migrate/20200330071706_create_channel_twilio_sms.rb create mode 100644 db/migrate/20200404135009_add_unique_validation_index.rb create mode 100644 spec/builders/contact_builder_spec.rb create mode 100644 spec/controllers/api/v1/accounts/channels/twilio_channels_controller_spec.rb create mode 100644 spec/controllers/twilio/callbacks_controller_spec.rb create mode 100644 spec/factories/channel/twilio_sms.rb create mode 100644 spec/services/twilio/incoming_message_service_spec.rb create mode 100644 spec/services/twilio/outgoing_message_service_spec.rb create mode 100644 spec/services/twilio/webhook_setup_service_spec.rb diff --git a/Gemfile b/Gemfile index 4a826347c..4225f2904 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,7 @@ gem 'uglifier' ##-- for active storage --## gem 'aws-sdk-s3', require: false -gem 'azure-storage', require: false +gem 'azure-storage-blob', require: false gem 'google-cloud-storage', require: false gem 'mini_magick' @@ -62,9 +62,9 @@ gem 'chargebee' ##--- gems for channels ---## gem 'facebook-messenger' gem 'telegram-bot-ruby' +gem 'twilio-ruby', '~> 5.32.0' # twitty will handle subscription of twitter account events gem 'twitty', git: 'https://github.com/chatwoot/twitty' - # facebook client gem 'koala' # Random name generator diff --git a/Gemfile.lock b/Gemfile.lock index a3374ffea..0ad798ef4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -102,15 +102,13 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - azure-core (0.1.15) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - nokogiri (~> 1.6) - azure-storage (0.15.0.preview) - azure-core (~> 0.1) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - nokogiri (~> 1.6, >= 1.6.8) + azure-storage-blob (2.0.0) + azure-storage-common (~> 2.0) + nokogiri (~> 1.10.4) + azure-storage-common (2.0.1) + faraday (~> 1.0) + faraday_middleware (~> 1.0.0.rc1) + nokogiri (~> 1.10.4) bcrypt (3.1.13) bindex (0.8.1) bootsnap (1.4.6) @@ -172,10 +170,10 @@ GEM railties (>= 4.2.0) faker (2.11.0) i18n (>= 1.6, < 2) - faraday (0.17.3) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - faraday_middleware (0.14.0) - faraday (>= 0.7.4, < 1.0) + faraday_middleware (1.0.0) + faraday (~> 1.0) ffi (1.12.2) flag_shih_tzu (0.3.23) foreman (0.87.1) @@ -410,8 +408,8 @@ GEM activerecord (>= 4) activesupport (>= 4) semantic_range (2.3.0) - sentry-raven (2.13.0) - faraday (>= 0.7.6, < 1.0) + sentry-raven (3.0.0) + faraday (>= 1.0) shoulda-matchers (4.3.0) activesupport (>= 4.2.0) sidekiq (6.0.6) @@ -449,6 +447,10 @@ GEM time_diff (0.3.0) activesupport i18n + twilio-ruby (5.32.0) + faraday (~> 1.0.0) + jwt (>= 1.5, <= 2.5) + nokogiri (>= 1.6, < 2.0) tzinfo (1.2.7) thread_safe (~> 0.1) tzinfo-data (1.2019.3) @@ -496,7 +498,7 @@ DEPENDENCIES annotate attr_extras aws-sdk-s3 - azure-storage + azure-storage-blob bootsnap brakeman browser @@ -553,6 +555,7 @@ DEPENDENCIES spring-watcher-listen telegram-bot-ruby time_diff + twilio-ruby (~> 5.32.0) twitty! tzinfo-data uglifier diff --git a/app/builders/contact_builder.rb b/app/builders/contact_builder.rb new file mode 100644 index 000000000..70b994ac2 --- /dev/null +++ b/app/builders/contact_builder.rb @@ -0,0 +1,37 @@ +class ContactBuilder + pattr_initialize [:source_id!, :inbox!, :contact_attributes!] + + def perform + contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id) + return contact_inbox if contact_inbox + + build_contact + end + + private + + def account + @account ||= inbox.account + end + + def build_contact + ActiveRecord::Base.transaction do + contact = account.contacts.create!( + name: contact_attributes[:name], + phone_number: contact_attributes[:phone_number], + email: contact_attributes[:email], + identifier: contact_attributes[:identifier], + additional_attributes: contact_attributes[:identifier] + ) + contact_inbox = ::ContactInbox.create!( + contact_id: contact.id, + inbox_id: inbox.id, + source_id: source_id + ) + ::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url] + contact_inbox + rescue StandardError => e + Rails.logger e + end + end +end diff --git a/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb new file mode 100644 index 000000000..c3d6554fd --- /dev/null +++ b/app/controllers/api/v1/accounts/channels/twilio_channels_controller.rb @@ -0,0 +1,50 @@ +class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController + before_action :authorize_request + + def create + authenticate_twilio + build_inbox + setup_webhooks + rescue Twilio::REST::TwilioError => e + render_could_not_create_error(e.message) + rescue StandardError => e + render_could_not_create_error(e.message) + end + + private + + def authorize_request + authorize ::Inbox + end + + def authenticate_twilio + client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token]) + client.messages.list(limit: 1) + end + + def setup_webhooks + ::Twilio::WebhookSetupService.new(inbox: @inbox).perform + end + + def build_inbox + ActiveRecord::Base.transaction do + twilio_sms = current_account.twilio_sms.create( + account_sid: permitted_params[:account_sid], + auth_token: permitted_params[:auth_token], + phone_number: permitted_params[:phone_number] + ) + @inbox = current_account.inboxes.create( + name: permitted_params[:name], + channel: twilio_sms + ) + rescue StandardError => e + render_could_not_create_error(e.message) + end + end + + def permitted_params + params.require(:twilio_channel).permit( + :account_id, :phone_number, :account_sid, :auth_token, :name + ) + end +end diff --git a/app/controllers/twilio/callback_controller.rb b/app/controllers/twilio/callback_controller.rb new file mode 100644 index 000000000..f6cb5356c --- /dev/null +++ b/app/controllers/twilio/callback_controller.rb @@ -0,0 +1,29 @@ +class Twilio::CallbackController < ApplicationController + def create + ::Twilio::IncomingMessageService.new(params: permitted_params).perform + + head :no_content + end + + private + + def permitted_params + params.permit( + :ApiVersion, + :SmsSid, + :From, + :ToState, + :ToZip, + :AccountSid, + :MessageSid, + :FromCountry, + :ToCity, + :FromCity, + :To, + :FromZip, + :Body, + :ToCountry, + :FromState + ) + end +end diff --git a/app/javascript/dashboard/api/channel/twilioChannel.js b/app/javascript/dashboard/api/channel/twilioChannel.js new file mode 100644 index 000000000..a688a1f11 --- /dev/null +++ b/app/javascript/dashboard/api/channel/twilioChannel.js @@ -0,0 +1,9 @@ +import ApiClient from '../ApiClient'; + +class TwilioChannel extends ApiClient { + constructor() { + super('channels/twilio_channel', { accountScoped: true }); + } +} + +export default new TwilioChannel(); diff --git a/app/javascript/dashboard/assets/images/channels/twilio.png b/app/javascript/dashboard/assets/images/channels/twilio.png new file mode 100644 index 0000000000000000000000000000000000000000..627a8e9d49d37d3055269051115c946f0398f234 GIT binary patch literal 12215 zcmch7byytD^Cu7>cyJbi6CijNcPIFw!6hv2?ht~z1-IZV?(QzZCAho01qrvw`+o0t z_q+S=p67OFo~h}suI{dxss2<|hbn?3(NTy{U|?X-rKQA`VPIgz{+vjN(2^`RtJlyA ztfR7|C`|bn@d5P4##lqzL|z_-9!ev@yn@AsdG&_{`iFr9!oWdkEa=G^Yz_new;TpW z9D0QjF|q|4lS^A0m>Me^8yLAmi(X~H{#V;9xc~6Oie?at>y8<`0yDDCFei{0>GYyvpJq|7{Mv6QDG6aR`t4IRwwoXl-)$p6SS0NXk{2~bl0 z>F9s{{vD^a-T&;!#_``-LFdQfW?;txWd6YNe{1AqZt`El``=pqsrjeb-&y_B89!7t zykZW<22QpPsHnkKzok&+3Zg)J`p<(BM4@9I6@Y4U!`z?Kj1nDBDQCS@|@h=|V zJ@Q5(mOQ97OBx}{_g`U<{(l_8x^T_qVvVnSt>J%@zHm2Z&kPoNnZ*;;ytTe|mD$N< zxO><>bWPGXaoFKYBeed#X-VI!Qda%TG~Z+mq(54|yiR~LLkj~e9WQQRezEm3O=pnz zgY>J5LNxD#M6*W{SXJ^9{f59PbZn1j2XC8$-ym5**_X%ilU!d&Gtq1dE2Qr)aqtCl zN0Lznxv2r~_GiLoRBS$((5HE+y~uTBfTZ{@4)|b045DRW8t(DwsWw!&im07M-+#QG z8yJt^UBb!zxg)%TQ%nA+0Ux%J_=RXyvZSJSe3|l5Vc&LM5`)8PH<2o#Y?-p<8xA2&@wL+HJ>Zwq_wq=;_+!%ZYHh%^5CtG?> z>D2v>2j0;+4tS<-D#^m}pBC1B2~{(c{VLiff);2Z7iUxjld7`@0kYUUN2cBfE{(yP zu`% z2*7Lj#gFVi0a?HY3S;8tPR=Y|6#teft+YLWp%eVPNF}D&YennVLo4gcMsI+~;Iz`D z^RS^LxxMkp$lLA~=XaPrv-`^);tUf^Yna)&bpSt)XKsq18%=lnCRThYCK1NK3%C`Z zcaB8bUQtyNFY9-GNWf*ufh{)tE?;1D5rfw*P)i&y{i~q9*)uKcz0`Ptc zzxV{(xmm9N`-d^!kZjt`VPIDus#CB4tO5-Yo92Gks|dR0amA2q!~SG|Jz67Imtjty zvzizh3D&N&UkdL-O8u}wXarTi09w7yx2p?6&{$RGBc|A++|S?(U0zPvXHk)8V)$rs zki8nJQ?x&5tzEg6*-VT^H(9}ErO~SnK$fMeDAhe6cA}%aB$vv z0ynZh!94^F6DJ9jbltLls72#5d2%R{1J(5IW4nqYpRFK`)Q*vUo^W`>d$WuiX;s3e3r4ZVaWhZtgCxl@QGLS$e@i{4 z1DmRuddP#83MbljJSDFyt{rQ8kZt^hBj8O^IPWH`S4ViA;KT$=ezm+P`5bD5&; zQQ^TfA2zE;jrmQebn-};dhm*eZ7zH!zk`X}I{XlX;;1`mH=a5q_c>YE0ALM#C;aXa z;x=mXWF}DkWlyCu9RE`KQFH@ENNubO35cv>_@b-9=&>^Twr6)0CwZEC^Q1kR>h{Y> zB{5k5=B?fv5A@GcTOqcOf>HC9K*m|UNl}YpL^G^gE!59mO0Nr$D4U@_<4WsOI(`1n-13!U(_;fRd@Ai7xw8BB1`Y38R10C4}jnuG|qH~D7|>d1 zzyQpLl6IZYT2{yW>XgY{mZ+3+Q!R88Yh{BOuu(i|mHuCw<|UBV9d4|M*0%vs@S8`4 zwpy%UlI>D?=W0>HGq)x4;b6WvExsNrU`p@%odjkaff!PDf($U1 zb0Hv*Ma6??c=FN<$+mnX8Q~kvFes!qx{2m>MYH)TO&`xMD~fzSwPEayZ1zn8K|Q zUdY=$_ooNS0Cd!?O8h=jUYSpraX3(>?8Tz-CIlqd<#BTf!1!&;@!m8p0+0@>0GohXs}LF`vGuEL z*q@!bXpE6pq{iN1?2~;}>K}y)wxcOIx|FN82zvMXVxozs z`X`j0+n$Bt5sU=HV-erfez_jKzB7fZ&i#a)gWppP({5(${`8&P5ALe+E=P)`G`h|e zy9{u9U-+BHIhd?ZYaRYDDehfD&Y(bIb{x_+BGCy0d=738xXGXAp~e)!8iYeN4g^xF zBh0GQvrdY#O|)V6@wAoxew?Q{&%x>W&Yaz(u^*v#aBoqsw_(SWDO`0jzP&87P0iDG zpNupv8gWYU{?(O!M~q*zr&3OQs(HqWV7phw@$S9gHq{Hx7XNnZJ5N*Z%9Nw`b3c=< zB4yCR?UTQ^HUu>HDioDmh%pWr_o?oSJ2b{?|pA&`Ue9FV&@^; zgD*{e=cCs*I0pyf564(;LhX+MF4g??1UZ8-Cd-tGg4x&-T<)Kv^c3-<#Z785#PJOM zF-7@47@`DTwZ1kC&8@}=A0xT|YqZbs?r3tS()G`0?Q3mv+yB&{lyKbOE{^HSl<^U1 z+qZEz+zP0&)ko2+QI6!hIm!Pv@TM=!VDkt$S2sVi7)iRCVQ1yjw>h$~6PmTbXwo=k zTCVB-K;P!RwSf;UG}7b`@%YSdbv`zh=9zHKnU^nNpI|SrZmaVX#dRbgm1?JM3hfrA zJ)f_{3l|SeU*yHsa@1~W1uYBEPDC5`4RhhnZHU@R>daH{7Q8sPIa#@gu+%f2aS~a+Yn~AIID6h zAu`HlVd-O+Qusbi(q7v$VR}#oyfD&Ve_@q`OE(aeCT9|vp_OGqN@^2n z8A2u*4J@~BWL2ZdB3yd_~` z9F5oKlEP2>@{S^xbj0%3zK@N_@l3t*3jPrLy9f-w8=(~JQs{_wfp~3&fibK45nI2u zW~1|z$C<>L<6_@(FxGXo5Q=C8(J|(CX$Gtg(^Hm8lLxY4i80n8PqD^-h}5gmIk77k zubOjvO|*;w`mD)$<9;6CBqfX#dA`aT;r&vT&9>X#=mSrc`myCJlf);h(P8U zIwJC}{f^1gmXN4sy{cP7=t{?@mBey0_VfYXZ6T?as^Adv<)E6eqB|LM=dK2!{=Sdkmp1|iH(K^wQ3OR(vV&yGi_IwO%vXhdh2QrMw`w#Ri2UM|$!u1i z6Ikz^)M;ef&q|#4<pHZ7!e`52;$MZO6l94E^rIR@TE_t}Er=pu&cMVsv34jqd z==w7PUJuZ3J8l%b^D=fR^r$}blZTyflfl}gdLr{w`iR$m`uEeGzpA+gbvGW#=^EpK z4w|7g9+Aiq^6<+(|1M53M`KCQXwi0fW#_auRneM;mB*Yo>-Y*i zPe}>VR{ab7Okk3>{8`{=x9qMzb|BG(tUGastLfS;?6S6dumr7@QkT+|PaUeFy*_04 z{=+rVO;XXMHKJE(zRo|gz)u|?0bAUzm^Ev`RkPo8)C<8?fRUq5S~NW@8~BhNr^<&G z-``0Z@Etd#+)w@2h9+iL5!~8S+Aw$0&8}e2gIy8b7>y$33L>tTz4OR0S+T{YArCh9 zr`i_pfkwd6&w340rk-V^sydWk0V|R#VQO4!{G_5d*!-5nH^J6+JoT?Yw_%c+yL-2y zPGsL0tnDd-hQkz(W1SWHmC;&v7<-krN`}Y)dXj(@z)>;sPus;4#d&32?Po+ z$At!hoamU5qHv#jQ16HC&^Y&f={WF{?1-PmTZB~oXQ>urMxV|hRz;L@#z$ZsMuuD- zH%YN(>ZNRgRAzk_c5zZ%H4HT@tqR7P0nK{U zHq-Z%jMm?^-qeVEU0w{eH}KHKv1W=#fHe2=HKOVeq!ujK&uS2e(Ldd#5#db;Xa$~b zXq%JJEYp`dbfQ4$D>>ID>oAxsBR>JR>jLAEb2$mIdZWL-1kg30lIF@Kl+yn3nV6Og^>V~HKtQ6z02*0nif{z9eoNq=Rn^NCBFtX=WIP*BOd zRxl^WKsn$rBnmI-oUGE#G&GWuSOlPJb+X9?$3FM%eU}=t^A=J33PJ71OQ*YO6NS=H z10&t-b;H16t1wFgkD8pi)8_Cp-KTPh*n_j1HkBob))BonPEX(uP#!@Iz4!Inw($+$ z4A)s%exr&Gs0Q_*vetd>s_d@IoB@B2i+U!u`QzdgsU^5DBGkC`!@Uj8n9-LC`;`$; zpYGeVrTfr~B>1%4#RRalXK9E*6(VD5;OqnCwoP1d4zvEbp6<<7OXR5aQ!hxxZ*-Vf z^lU6@y+-Hj%JYOe19<3;sUmwlHTRD?u?nX*H$$}l4Wu>g~$et_8q|%jx|D*5m%S(sKyM7bISj9C3)N{l-Z;I86C zEt4%FoY9NZ3OBeYs0M)Q@E2RTv^um$y9J+Y{tBzXwXB(Fu1#FaRZg3=A#1l{3i<=| zd%A*Ms}%jE?^Ue|$W=U=-MLQq{yZF7moR1PM6IORtClj(I{Y4;9onN%OgDi?mj?@) z54wl?)9N@f>Y2?yuBLWBF2I5!?RcDZX7f{~r5NE?)zY-WvpFC5P!C*);L!_E$hJbj zO@8_OhhR&Bj>KO$*74@gvC)o-WD|2mY3+fel9^Q34%s|(4l$3{XAe~vj&c;|1Bwl_Lj<2DXC6i* z`Rlhd_7(2lk2Iawy$axBv9`zN;O6OCd_RG7s9_js!5HKd?L*9wgWqY7_NsNM(vC~< zC;|a^_-5&Ijb|&ytMu8>`;MsWD2|`S{a`mLzc`CpR<@VE9#BM|TLkBu;{beeT$z?= zAhjJzOap^bzv4iu#V11`JH74lla9gu4n)mAB`JhhLOCM5?|n$T3BguC%Lo5yX4ccQ zfxo<{!1#{-^O^_K!*=e*NQ&mLQ%xj>hjrE1^G6afl3eNU_BiyBeU;Md*~`tu6_@5J z?05n(m}>5@AUtHBwW67-Phe>ED7gP8lI}~Q(9T)}jJi{5Orw1J?x>fC{V0xL11j(m zd*xKB)12$Axw6X&Uy+%oS8X;uVwgm8mXPa-|R4OlTgk)yIVL%CT_An@5dDg zuCFrtR!iIltis(gLZR%m((xO*pzIZm_5ii?;-27lQdY=cq<0d@)@u=DDr`8Wi}Atl z(@fC9MXa$(>G=Zh{iqdf<=s+DAP<7qBqC(P!##IE1cK%{tpyV&!$gX(x9`#$^KcNu(Z^1U=nLE+d^u?twGMe)g-3Fh?B8H>Cv?)9g@$ILWp#N zTFOBh7+`#ckQ52h*RQnDh?GJW7CMx)f|3ir{AZvXZj=*JXlbO0`$LH0ui(gG;4+Ze zvBmO#XWy7s3u!U+LXt(FpG|R zDhvZJOwI|1*)#8ec7OnjX(tK;-iK#^gOVYUFufn@iN!obVBpg+J6DZpHF^-kULhS` zyoPwBKWx(T0NE{ohqAk6i%lK@1R3vGTX~DO4(Ey=k2;bdixYHQkdK5Cv^bNOc}`L1 zT6K8oYUWOlZKamZKhzuCyncZc(QXC_S|=_hHp+Wgf?J3LdnOhSEQn_b5nTJbrD%18 zqA%>`=zThiC=KqG-V4Y`q9j-VO$JTZHgLYdsGt4~$nEYc@a{YS^ZRxde^3l`+1sZR zyGkvk^`e91&GPr_Q`G99TAN61CsnNHNGp2$(C)Y^4L?WqLK&7GbQ3&P-ZkVMm8UlM z?TAL!C&7G)@<&;^@2^93-o=+ZYe3=?IvqNT;Bu>@PqxrXTs;GFI^&K*+2>2FNTxXl zLjPQRCb5Nz*Ln64X3h4_s7T`;4wf+}tJ1ncrz;1P^68v+_CLd6(+};dcVxckS{8hY z>{EO=f<1VdnO8HBMG9{5-0`G&7xol0O_RNZ2Kp^qa@{uG`4(%uaf#TD7%TlL$xrogp1!FcEcJq3Py#FUJh^@Fn zR(zkaQLe;h^L{=Q{LcSfKROQ#JziR^l1L_xNw9x9NHv1I!4il5Gr#P{5eJqYRczyl?Tu#Mq zYdn3(`;H|G6-%HMoAy?V&QuE@$a`RcQILw?d$e@n>@T)GRY+)sR9h$LLkf)xNA;~Q z>*@f|bV+UKL=71c*OUD=&zRO^f@NJ$9Wi9XY~IA12s-;A{n7q;p6JS@qoPMsTDv2Q?MaY205eV&ttE1-8=aHR*mY63PGUGGXTt?m6@XJZ zP9rUFHvd!o2?rF#O?^Jxv0%;neOg`GlOg|2?{s_Y#}P#X6yatbRrR}4kDH`8o3WX; zm?_f&i3`1|Q;2i)axdzAmh;K znzmc1Twv+_aa%47!iTxv&Fg^D)0=Cj)#ghDIBBTMLJSPzXIZQX_qd4}&bIec`x*Uu z>{}KaPdomMr*z=$QXSOU@z{%?KB=M!(uLG{<7;@#^?GMsScf0|fxgXakn+pcTKBh6 z8F!uLv_z0M@1PD8;$H_!gSjf@CkJ_D%Vh9HMop)A)_%+Q*T9Bs8TeQ8UMK? zpjjy{TYj{PAt)u(csWMtwJiy$g8r3Iw4hVMUVa%z_`S2~x>H8*aLJ%npp5nmaS0fp zsh+Y%jMM5YwO1kFs@X^#VBPcat%20%Di(A~D_lj|TcDPwNm=;Fh8CU}Mhq)%nHZwd zRa{B*_@McZvL1JHLwU-j4Bu%NX3n$zS^C;Cj@iBs>I+SyXbz-m>n{O)M56)P!OM$P zRfuTm>2vT{0{Jv*i?c*W;QN#7pAiHioIv`@rFFw#PMr52hLWQ za7sh{ZBY6wO}YJtTp#2nhI}&~^Q%FV{xGEQzhKx377`Y)a+Y(fLQ?s*cxgh%ECL6} zc!%82j)Cu`{^R0$l;=i;#wI*wd}7qdJZ^=%Ylv9^gQ<9hM)qot^OmwA&Iua9m=(V7 z%RTBpZ7H~sdGi8I_b%@B-_!*Dc8cB%ImjGjes?vMs5{QL@)`oVFL&Q%9XEbil)oT& z=r)PYWt|HJIIO3PZ5`4xa1bZ+o2mP6roG}lFYF-_8P7CoFqzEZmS z`n59c135GvN&^4%BgFZ3P8uI92iCw7y8b>euDDE1xP7;-I~u5Ekhp!^IDPoRAgNlX zaRwIx25dmPdMYGV#Lz;?DcS!XSowq4u2_owk$L#OFr{L>JVP#BRgDKvjC2XBs;{uR zewPdVy-?!0$5%6Y4|0mqY88Z^;8rw=g2gWP=@f-!stLBL|k<8X|n~b%G<}F zHiQ}eA9Q^yoo>8~oVgSTSQz^nwBf7b{aN{k%QG&EZu2bm9yBzgY<7L#TC|b9V(Ec3 z_IR;En?2oiarNJj_6k1?q$jUY&T&ETSUDBdyL|@R^?;K3RH!vEwBY>3i7f6J(6~cY z)J?Edkl2DC$`qVD-IWmit`_lbhZ7nR+*mM7Wp{7+CH^}jaI9TH6pipEIL-Av&UL8Q z@msodrIO>kWYgLSM))#4H)L(*}uAe9^Tys8c)+YjwKMPAT&yBN_D%Lx%vGEk8>n^ z>zCpxt8sE9q{q-Q zx7^~w5gt~28bsvP*Qx<2LG#u(o>;nX*c@?ptaQX9VpV0~e^e6{VJgdx)~LA=^cWK` zt~x#z(h2;wyINymOi+h^1qVX1BiyMzd;1i9f)18qh8|N^Iu*A^}R}!kVMBQA2?^1{q- z_(A{Tg5`EVOKTD|cw=|tO?=zv_*uN6Va(ZK??|2^XggI-yasv^rDw5I7RaDrQi)-raE=;7cV29M!Q}g z!6m(Zf@?jzDg-0j9~TouzL(zAEblv2EPYW>r5@hW^o3LGnU0)!m1&Zi@@Fxcr-+O? z^`s!c?PybJB1z)=#SsMzx<*%YV=u|;$-iqz-}k8ZV5wT8GQ4a!8*t5hXF@Q-BmDI$ z{px#qBnEfnuZz;so*e6t9OFUw;h%St?JspUbnCDtr0QKmT+X3=2T{XUezO?L2y$O2 znG)|JC8>gRw!<@s510g3w9?EqCshRZl@C8(&5CdBioo@KBXkzuT0guH8KC3N^7$Ll z0VZQ709Yq>EP1!ST&er}`BwEJR_5`Y49&00aPD7i4X=@vE@M8k@dD-CfrsC}iL#9m zX23mJ)DNJOB2%B=-49H5Zg(hNe#@ZKlN|36sIq1b0ih9kG@)%MJn-m@2Lx2?%{YIF zDtF}PX`Q0}GT%@wgF~}6z}btF4{&gPPpi6mTN+b!;Fv#}tI==3-R1ohzqPbXbUZC& zHFjt=ei5ODmg12~qdLD@Hy$B=K__k>DYHIYEhkuD6ahy|Eq9G2pIrJXObcxxneVx103DdDmqxvn5LuhsPiZ{HXA%Whd-J2! zZ&aR5hoxI0OoGr^Vh@&j2h|j5x~bW<(X-QrLMYxMi7bB~)%pBycHe54q+Wabj5(>u z_u+5`@Ek)wJg+};X(%I&l;WZc9z+zuOzCcq7KCW-uSG#BSZbU4M^5PV9N{ihTqrIeVj zIwmp-hCH!p3ZX*@gmIN?c~u=aIUjO_ytFW-bdd<9FTsN!MKM$SR^F&%(DSzzTqrZJ zO7&UK@RFQe?%J5)>rIJW+b)tib?lO!dmujDiCz!4wk>gj?Rfrj~#`&hL}W z^=SQ$u92^8``z@v=NLBzf_S3t!NXL`aK7wF;!m42>V(iedm{D3l1~Yw(JtIw)|er& zos84Fq#&WSe|8q(xHr3u#Mv7JTT)~IVD3>|SP z^4mGL2Vo3gsdP%qC~B@g=7;nk)d##|+6sVTfHaUnJktAWtx}uILf=FQ1R@b7> z1hZd$M3-24`F~}ByFLtrtjXyMUEt{#UkIZy?OI(NO4npniF<>A8t@2kd`JMIazg(7 z;Sya};YydcU^DccPr}1Vu?&LvR!+_Y9m1VNzA00OlNZ+w`? zqNh?yaGi97DdtmJ(Fd>88uW@lA4wR#BkTO>6?mWU-opvA&zaJ@EeI)S%ss?lpZNXO zfOvRJ#QAqLxd=DOb}G@34U7-jHZ|`}=!JT|r7QBRQ?ELN%DF@>7_hWjO27e21BW`O z^dQ`d`WZyIWXd!fXc8})p9MaOnW9=)e`lG2pqAp)Mm0Eo#q1eFT6taRMJD?(ykV5C zF9>1zGsaxZ-mJ`BWBe{ zqNcml&0Xk2$c`95+(Zi9IF;5$8DrWX$&c|(Tjd*FFEp^Ha13So7^;H4FgBN=HyFJi zX46YzPc0&pQjYOy^etePPV$oBm3V!EQ`T2vz&>!m2!Ypc^ExazEDWWHeABvzH4Oi4COLfL{Vt+~lW# { switch (type) { @@ -68,6 +69,9 @@ const getInboxClassByType = type => { case INBOX_TYPES.TWITTER: return 'ion-social-twitter'; + case INBOX_TYPES.TWILIO: + return 'ion-android-textsms'; + default: return ''; } diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue index da22f71bd..948bff103 100644 --- a/app/javascript/dashboard/components/widgets/ChannelItem.vue +++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue @@ -24,6 +24,10 @@ v-if="channel === 'website'" src="~dashboard/assets/images/channels/website.png" /> +

{{ channel }}

@@ -39,7 +43,7 @@ export default { }, methods: { isActive(channel) { - return ['facebook', 'website', 'twitter'].includes(channel); + return ['facebook', 'website', 'twitter', 'twilio'].includes(channel); }, onItemClick() { if (this.isActive(this.channel)) { diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index d84be8f1e..0dcdd60d5 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -21,6 +21,7 @@ "WEBSITE_CHANNEL": { "TITLE": "Website channel", "DESC": "Create a channel for your website and start supporting your customers via our website widget.", + "LOADING_MESSAGE": "Creating Website Support Channel", "CHANNEL_NAME": { "LABEL": "Website Name", "PLACEHOLDER": "Enter your website name (eg: Acme Inc)" @@ -35,6 +36,34 @@ }, "SUBMIT_BUTTON":"Create inbox" }, + "TWILIO": { + "TITLE": "Twilio SMS Channel", + "DESC": "Integrate Twilio and start supporting your customers via SMS.", + "ACCOUNT_SID": { + "LABEL": "Account SID", + "PLACEHOLDER": "Please enter your Twilio Account SID", + "ERROR": "This field is required" + }, + "AUTH_TOKEN": { + "LABEL": "Auth Token", + "PLACEHOLDER": "Please enter your Twilio Auth Token", + "ERROR": "This field is required" + }, + "CHANNEL_NAME": { + "LABEL": "Channel Name", + "PLACEHOLDER": "Please enter a channel name", + "ERROR": "This field is required" + }, + "PHONE_NUMBER": { + "LABEL": "Phone number", + "PLACEHOLDER": "Please enter the phone number from which message will be sent.", + "ERROR": "Please enter a valid value. Phone number should start with `+` sign." + }, + "SUBMIT_BUTTON": "Create Twilio Channel", + "API": { + "ERROR_MESSAGE": "We were not able to authenticate Twilio credentials, please try again" + } + }, "AUTH": { "TITLE": "Channels", "DESC": "Currently we support Website live chat widgets, Facebook Pages and Twitter profiles as platforms. We have more platforms like Whatsapp, Email, Telegram and Line in the works, which will be out soon." diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue index 728d753d9..0fa469d9b 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/AddAgent.vue @@ -128,11 +128,11 @@ export default { this.showAlert(this.$t('AGENT_MGMT.ADD.API.SUCCESS_MESSAGE')); this.onClose(); } catch (error) { - if (error.response.status === 422) { - this.showAlert(this.$t('AGENT_MGMT.ADD.API.EXIST_MESSAGE')); - } else { - this.showAlert(this.$t('AGENT_MGMT.ADD.API.ERROR_MESSAGE')); - } + if (error.response.status === 422) { + this.showAlert(this.$t('AGENT_MGMT.ADD.API.EXIST_MESSAGE')); + } else { + this.showAlert(this.$t('AGENT_MGMT.ADD.API.ERROR_MESSAGE')); + } } }, }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue index 3b3ba9dc3..12bba91d3 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue @@ -27,7 +27,14 @@ export default { }, data() { return { - channelList: ['website', 'facebook', 'twitter', 'telegram', 'line'], + channelList: [ + 'website', + 'facebook', + 'twitter', + 'twilio', + 'telegram', + 'line', + ], }; }, methods: { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue index 9da9ccfab..accba4cfb 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/FinishSetup.vue @@ -28,12 +28,14 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue index 8641210db..83bf17866 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Website.vue @@ -5,11 +5,15 @@ :header-content="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.DESC')" /> -
+