diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index 18538e842..af96441f8 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -26,9 +26,8 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController @portal.update!(portal_params.merge(live_chat_widget_params)) if params[:portal].present? # @portal.custom_domain = parsed_custom_domain process_attached_logo if params[:blob_id].present? - rescue StandardError => e - Rails.logger.error e - render json: { error: @portal.errors.messages }.to_json, status: :unprocessable_entity + rescue ActiveRecord::RecordInvalid => e + render_record_invalid(e) end end diff --git a/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb b/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb index e7a1f3fa6..3e7d876c3 100644 --- a/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb +++ b/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb @@ -1,8 +1,10 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts::BaseController before_action :validate_feature_enabled! + before_action :fetch_and_validate_inbox, if: -> { params[:inbox_id].present? } # POST /api/v1/accounts/:account_id/whatsapp/authorization - # Handles the embedded signup callback data from the Facebook SDK + # Handles both initial authorization and reauthorization + # If inbox_id is present in params, it performs reauthorization def create validate_embedded_signup_params! channel = process_embedded_signup @@ -16,21 +18,42 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts: def process_embedded_signup service = Whatsapp::EmbeddedSignupService.new( account: Current.account, - code: params[:code], - business_id: params[:business_id], - waba_id: params[:waba_id], - phone_number_id: params[:phone_number_id] + params: params.permit(:code, :business_id, :waba_id, :phone_number_id).to_h.symbolize_keys, + inbox_id: params[:inbox_id] ) service.perform end - def render_success_response(inbox) + def fetch_and_validate_inbox + @inbox = Current.account.inboxes.find(params[:inbox_id]) + validate_reauthorization_required + end + + def validate_reauthorization_required + return if @inbox.channel.reauthorization_required? || can_upgrade_to_embedded_signup? + render json: { + success: false, + message: I18n.t('inbox.reauthorization.not_required') + }, status: :unprocessable_entity + end + + def can_upgrade_to_embedded_signup? + channel = @inbox.channel + return false unless channel.provider == 'whatsapp_cloud' + + true + end + + def render_success_response(inbox) + response = { success: true, id: inbox.id, name: inbox.name, channel_type: 'whatsapp' } + response[:message] = I18n.t('inbox.reauthorization.success') if params[:inbox_id].present? + render json: response end def render_error_response(error) diff --git a/app/controllers/twilio/callback_controller.rb b/app/controllers/twilio/callback_controller.rb index 455828228..d607ba151 100644 --- a/app/controllers/twilio/callback_controller.rb +++ b/app/controllers/twilio/callback_controller.rb @@ -30,7 +30,8 @@ class Twilio::CallbackController < ApplicationController :NumMedia, :Latitude, :Longitude, - :MessageType + :MessageType, + :ProfileName ) end end diff --git a/app/finders/notification_finder.rb b/app/finders/notification_finder.rb index ccfe470a0..e1958827a 100644 --- a/app/finders/notification_finder.rb +++ b/app/finders/notification_finder.rb @@ -15,7 +15,13 @@ class NotificationFinder end def unread_count - @notifications.where(read_at: nil).count + if type_included?('read') + # If we're including read notifications, filter to unread + @notifications.where(read_at: nil).count + else + # Already filtered to unread notifications, just count + @notifications.count + end end def count @@ -27,7 +33,7 @@ class NotificationFinder def set_up find_all_notifications filter_snoozed_notifications - fitler_read_notifications + filter_read_notifications end def find_all_notifications @@ -38,7 +44,7 @@ class NotificationFinder @notifications = @notifications.where(snoozed_until: nil) unless type_included?('snoozed') end - def fitler_read_notifications + def filter_read_notifications @notifications = @notifications.where(read_at: nil) unless type_included?('read') end diff --git a/app/javascript/dashboard/api/channel/whatsappChannel.js b/app/javascript/dashboard/api/channel/whatsappChannel.js index e1003b123..8f51f4878 100644 --- a/app/javascript/dashboard/api/channel/whatsappChannel.js +++ b/app/javascript/dashboard/api/channel/whatsappChannel.js @@ -9,6 +9,13 @@ class WhatsappChannel extends ApiClient { createEmbeddedSignup(params) { return axios.post(`${this.baseUrl()}/whatsapp/authorization`, params); } + + reauthorizeWhatsApp({ inboxId, ...params }) { + return axios.post(`${this.baseUrl()}/whatsapp/authorization`, { + ...params, + inbox_id: inboxId, + }); + } } export default new WhatsappChannel(); diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue index 0932a79c7..0e893b767 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue @@ -98,6 +98,7 @@ const onClickViewDetails = () => emit('showContact', props.id); :src="thumbnail" :size="48" :status="availabilityStatus" + hide-offline-status rounded-full />
diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue index 7995270b5..862a2cd67 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalBaseSettings.vue @@ -7,8 +7,8 @@ import { useStore, useStoreGetters } from 'dashboard/composables/store'; import { uploadFile } from 'dashboard/helper/uploadHelper'; import { checkFileSizeLimit } from 'shared/helpers/FileHelper'; import { useVuelidate } from '@vuelidate/core'; -import { required, minLength, helpers } from '@vuelidate/validators'; -import { shouldBeUrl, isValidSlug } from 'shared/helpers/Validators'; +import { required, minLength, helpers, url } from '@vuelidate/validators'; +import { isValidSlug } from 'shared/helpers/Validators'; import Button from 'dashboard/components-next/button/Button.vue'; import Input from 'dashboard/components-next/input/Input.vue'; @@ -71,7 +71,7 @@ const rules = { isValidSlug ), }, - homePageLink: { shouldBeUrl }, + homePageLink: { url }, }; const v$ = useVuelidate(rules, state); diff --git a/app/javascript/dashboard/components-next/avatar/Avatar.vue b/app/javascript/dashboard/components-next/avatar/Avatar.vue index 65859b1c7..08224995c 100644 --- a/app/javascript/dashboard/components-next/avatar/Avatar.vue +++ b/app/javascript/dashboard/components-next/avatar/Avatar.vue @@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'; import { removeEmoji } from 'shared/helpers/emoji'; import Icon from 'dashboard/components-next/icon/Icon.vue'; +import ChannelIcon from 'dashboard/components-next/icon/ChannelIcon.vue'; import wootConstants from 'dashboard/constants/globals'; const props = defineProps({ @@ -33,10 +34,18 @@ const props = defineProps({ validator: value => !value || wootConstants.AVAILABILITY_STATUS_KEYS.includes(value), }, + inbox: { + type: Object, + default: null, + }, iconName: { type: String, default: null, }, + hideOfflineStatus: { + type: Boolean, + default: false, + }, }); const emit = defineEmits(['upload', 'delete']); @@ -66,11 +75,11 @@ const AVATAR_COLORS = { default: { bg: '#E8E8E8', text: '#60646C' }, }; -const STATUS_CLASSES = { +const STATUS_CLASSES = computed(() => ({ online: 'bg-n-teal-10', busy: 'bg-n-amber-10', - offline: 'bg-n-slate-10', -}; + ...(props.hideOfflineStatus ? {} : { offline: 'bg-n-slate-10' }), +})); const showDefaultAvatar = computed(() => !props.src && !props.name); @@ -178,11 +187,18 @@ watch(
+
+ +
@@ -239,24 +255,33 @@ watch( - + + diff --git a/app/javascript/dashboard/components-next/icon/ChannelIcon.vue b/app/javascript/dashboard/components-next/icon/ChannelIcon.vue index 11117cc18..68102dbd3 100644 --- a/app/javascript/dashboard/components-next/icon/ChannelIcon.vue +++ b/app/javascript/dashboard/components-next/icon/ChannelIcon.vue @@ -1,4 +1,5 @@