diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 757af9b62..9b8f7b747 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -97,7 +97,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController end def allowed_channel_types - %w[web_widget api email line telegram whatsapp sms] + %w[web_widget api email line telegram whatsapp sms app_store] end def update_inbox_working_hours @@ -179,7 +179,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController 'line' => Channel::Line, 'telegram' => Channel::Telegram, 'whatsapp' => Channel::Whatsapp, - 'sms' => Channel::Sms + 'sms' => Channel::Sms, + 'app_store' => Channel::AppStore }[permitted_params[:channel][:type]] end diff --git a/app/helpers/api/v1/inboxes_helper.rb b/app/helpers/api/v1/inboxes_helper.rb index 8a10fa99c..3920ecfa4 100644 --- a/app/helpers/api/v1/inboxes_helper.rb +++ b/app/helpers/api/v1/inboxes_helper.rb @@ -111,7 +111,8 @@ module Api::V1::InboxesHelper 'line' => Current.account.line_channels, 'telegram' => Current.account.telegram_channels, 'whatsapp' => Current.account.whatsapp_channels, - 'sms' => Current.account.sms_channels + 'sms' => Current.account.sms_channels, + 'app_store' => Current.account.app_store_channels }[permitted_params[:channel][:type]] end diff --git a/app/javascript/dashboard/components-next/icon/provider.js b/app/javascript/dashboard/components-next/icon/provider.js index d7a9c93ad..ebb168f3d 100644 --- a/app/javascript/dashboard/components-next/icon/provider.js +++ b/app/javascript/dashboard/components-next/icon/provider.js @@ -15,6 +15,7 @@ export function useChannelIcon(inbox) { 'Channel::Whatsapp': 'i-woot-whatsapp', 'Channel::Instagram': 'i-woot-instagram', 'Channel::Tiktok': 'i-woot-tiktok', + 'Channel::AppStore': 'i-ri-app-store-fill', }; const providerIconMap = { diff --git a/app/javascript/dashboard/components-next/message/MessageMeta.vue b/app/javascript/dashboard/components-next/message/MessageMeta.vue index f26339cbb..e0c1f071e 100644 --- a/app/javascript/dashboard/components-next/message/MessageMeta.vue +++ b/app/javascript/dashboard/components-next/message/MessageMeta.vue @@ -20,6 +20,7 @@ const { isAWhatsAppChannel, isAnEmailChannel, isAnInstagramChannel, + isAnAppStoreChannel, isATiktokChannel, } = useInbox(); @@ -62,7 +63,8 @@ const isSent = computed(() => { isASmsInbox.value || isATelegramChannel.value || isAnInstagramChannel.value || - isATiktokChannel.value + isATiktokChannel.value || + isAnAppStoreChannel.value ) { return sourceId.value && status.value === MESSAGE_STATUS.SENT; } @@ -85,7 +87,8 @@ const isDelivered = computed(() => { isASmsInbox.value || isAFacebookInbox.value || isAnInstagramChannel.value || - isATiktokChannel.value + isATiktokChannel.value || + isAnAppStoreChannel.value ) { return sourceId.value && status.value === MESSAGE_STATUS.DELIVERED; } @@ -110,7 +113,8 @@ const isRead = computed(() => { isATwilioChannel.value || isAFacebookInbox.value || isAnInstagramChannel.value || - isATiktokChannel.value + isATiktokChannel.value || + isAnAppStoreChannel.value ) { return sourceId.value && status.value === MESSAGE_STATUS.READ; } diff --git a/app/javascript/dashboard/components/widgets/ChannelItem.vue b/app/javascript/dashboard/components/widgets/ChannelItem.vue index 2cbad72c5..54c9c707d 100644 --- a/app/javascript/dashboard/components/widgets/ChannelItem.vue +++ b/app/javascript/dashboard/components/widgets/ChannelItem.vue @@ -66,6 +66,7 @@ const isActive = computed(() => { 'line', 'instagram', 'tiktok', + 'app_store', 'voice', ].includes(key); }); @@ -78,7 +79,7 @@ const isComingSoon = computed(() => { }); const isBeta = computed(() => { - return ['tiktok', 'voice'].includes(props.channel.key); + return ['tiktok', 'app_store', 'voice'].includes(props.channel.key); }); const onItemClick = () => { diff --git a/app/javascript/dashboard/composables/useInbox.js b/app/javascript/dashboard/composables/useInbox.js index 6e8d0a52a..bb6a01374 100644 --- a/app/javascript/dashboard/composables/useInbox.js +++ b/app/javascript/dashboard/composables/useInbox.js @@ -22,6 +22,7 @@ export const INBOX_FEATURE_MAP = { INBOX_TYPES.WHATSAPP, INBOX_TYPES.TELEGRAM, INBOX_TYPES.TIKTOK, + INBOX_TYPES.APP_STORE, INBOX_TYPES.API, ], [INBOX_FEATURES.REPLY_TO_OUTGOING]: [ @@ -30,6 +31,7 @@ export const INBOX_FEATURE_MAP = { INBOX_TYPES.WHATSAPP, INBOX_TYPES.TELEGRAM, INBOX_TYPES.TIKTOK, + INBOX_TYPES.APP_STORE, INBOX_TYPES.API, ], }; @@ -138,6 +140,10 @@ export const useInbox = (inboxId = null) => { return channelType.value === INBOX_TYPES.TIKTOK; }); + const isAnAppStoreChannel = computed(() => { + return channelType.value === INBOX_TYPES.APP_STORE; + }); + const voiceCallEnabled = computed(() => isVoiceCallEnabled(inbox.value)); const voiceCallProvider = computed(() => getVoiceCallProvider(inbox.value)); @@ -160,6 +166,7 @@ export const useInbox = (inboxId = null) => { isAnEmailChannel, isAnInstagramChannel, isATiktokChannel, + isAnAppStoreChannel, voiceCallEnabled, voiceCallProvider, }; diff --git a/app/javascript/dashboard/constants/editor.js b/app/javascript/dashboard/constants/editor.js index 378d303b4..a89d197a9 100644 --- a/app/javascript/dashboard/constants/editor.js +++ b/app/javascript/dashboard/constants/editor.js @@ -114,6 +114,11 @@ export const FORMATTING = { nodes: [], menu: [], }, + 'Channel::AppStore': { + marks: [], + nodes: [], + menu: [], + }, // Special contexts (not actual channels) 'Context::PrivateNote': { marks: ['strong', 'em', 'code', 'link', 'strike'], diff --git a/app/javascript/dashboard/helper/inbox.js b/app/javascript/dashboard/helper/inbox.js index 4039a07d1..65d04b2ca 100644 --- a/app/javascript/dashboard/helper/inbox.js +++ b/app/javascript/dashboard/helper/inbox.js @@ -11,6 +11,7 @@ export const INBOX_TYPES = { SMS: 'Channel::Sms', INSTAGRAM: 'Channel::Instagram', TIKTOK: 'Channel::Tiktok', + APP_STORE: 'Channel::AppStore', }; // Add providers here as they gain voice capability (e.g., WhatsApp Cloud, Twilio WhatsApp) @@ -50,6 +51,7 @@ const INBOX_ICON_MAP_FILL = { [INBOX_TYPES.LINE]: 'i-ri-line-fill', [INBOX_TYPES.INSTAGRAM]: 'i-ri-instagram-fill', [INBOX_TYPES.TIKTOK]: 'i-ri-tiktok-fill', + [INBOX_TYPES.APP_STORE]: 'i-ri-app-store-fill', }; const DEFAULT_ICON_FILL = 'i-ri-chat-1-fill'; @@ -65,6 +67,7 @@ const INBOX_ICON_MAP_LINE = { [INBOX_TYPES.LINE]: 'i-woot-line', [INBOX_TYPES.INSTAGRAM]: 'i-woot-instagram', [INBOX_TYPES.TIKTOK]: 'i-woot-tiktok', + [INBOX_TYPES.APP_STORE]: 'i-ri-app-store-line', }; const DEFAULT_ICON_LINE = 'i-ri-chat-1-line'; @@ -114,6 +117,9 @@ export const getReadableInboxByType = (type, phoneNumber) => { case INBOX_TYPES.LINE: return 'line'; + case INBOX_TYPES.APP_STORE: + return 'app_store'; + default: return 'chat'; } diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 2620f89f7..9b9df0fa2 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -382,6 +382,39 @@ "ERROR_MESSAGE": "We were not able to save the api channel" } }, + "APP_STORE": { + "TITLE": "App Store Reviews", + "DESC": "Manage and reply to your App Store reviews from Chatwoot.", + "CHANNEL_NAME": { + "LABEL": "Inbox Name", + "PLACEHOLDER": "Please enter an inbox name", + "ERROR": "This field is required" + }, + "APP_ID": { + "LABEL": "App Store Connect app ID", + "PLACEHOLDER": "Enter your App Store Connect app ID", + "ERROR": "This field is required" + }, + "ISSUER_ID": { + "LABEL": "Issuer ID", + "PLACEHOLDER": "Enter your App Store Connect issuer ID", + "ERROR": "This field is required" + }, + "KEY_ID": { + "LABEL": "Key ID", + "PLACEHOLDER": "Enter your App Store Connect key ID", + "ERROR": "This field is required" + }, + "PRIVATE_KEY": { + "LABEL": "Private key", + "PLACEHOLDER": "Paste the contents of your .p8 private key", + "ERROR": "This field is required" + }, + "SUBMIT_BUTTON": "Create App Store Channel", + "API": { + "ERROR_MESSAGE": "We were not able to save the App Store channel" + } + }, "EMAIL_CHANNEL": { "TITLE": "Email Channel", "DESC": "Integrate your email inbox.", @@ -493,6 +526,10 @@ "TITLE": "TikTok", "DESCRIPTION": "Connect your TikTok account" }, + "APP_STORE": { + "TITLE": "App Store Reviews", + "DESCRIPTION": "Manage your App Store app reviews" + }, "VOICE": { "TITLE": "Voice", "DESCRIPTION": "Integrate with Twilio Voice" @@ -1165,6 +1202,7 @@ "API": "API Channel", "INSTAGRAM": "Instagram", "TIKTOK": "TikTok", + "APP_STORE": "App Store Reviews", "VOICE": "Voice" } } diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelFactory.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelFactory.vue index 7d1d58854..fec8090a1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelFactory.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelFactory.vue @@ -12,6 +12,7 @@ import Telegram from './channels/Telegram.vue'; import Instagram from './channels/Instagram.vue'; import Tiktok from './channels/Tiktok.vue'; import Voice from './channels/Voice.vue'; +import AppStore from './channels/AppStore.vue'; const channelViewList = { facebook: Facebook, @@ -25,6 +26,7 @@ const channelViewList = { telegram: Telegram, instagram: Instagram, tiktok: Tiktok, + app_store: AppStore, voice: Voice, }; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue index e2ebd27cd..0a7f191ff 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/ChannelList.vue @@ -77,6 +77,12 @@ const channelList = computed(() => { description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.INSTAGRAM.DESCRIPTION'), icon: 'i-woot-instagram', }, + { + key: 'app_store', + title: t('INBOX_MGMT.ADD.AUTH.CHANNEL.APP_STORE.TITLE'), + description: t('INBOX_MGMT.ADD.AUTH.CHANNEL.APP_STORE.DESCRIPTION'), + icon: 'i-ri-app-store-line', + }, ]; if (hasTiktokConfigured.value) { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index a26eb0e18..2e5e44724 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -249,6 +249,13 @@ export default { ]; } + if (this.isAnAppStoreChannel) { + const unsupportedKeys = ['business-hours', 'csat', 'bot-configuration']; + visibleToAllChannelTabs = visibleToAllChannelTabs.filter( + tab => !unsupportedKeys.includes(tab.key) + ); + } + return visibleToAllChannelTabs; }, currentInboxId() { @@ -282,6 +289,9 @@ export default { if (this.isAnEmailChannel) { return `${this.inbox.name} (${this.inbox.email})`; } + if (this.isAnAppStoreChannel && this.inbox.app_id) { + return `${this.inbox.name} (${this.inbox.app_id})`; + } return this.inbox.name; }, canLocktoSingleConversation() { @@ -293,6 +303,7 @@ export default { this.isAnInstagramChannel || this.isALineChannel || this.isATiktokChannel || + this.isAnAppStoreChannel || this.isATelegramChannel ); }, @@ -829,6 +840,7 @@ export default { @@ -1128,6 +1140,7 @@ export default { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/AppStore.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/AppStore.vue new file mode 100644 index 000000000..6eb5b6073 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/AppStore.vue @@ -0,0 +1,176 @@ + + +