diff --git a/Gemfile b/Gemfile index 2e1e3ec14..f18305970 100644 --- a/Gemfile +++ b/Gemfile @@ -181,6 +181,9 @@ gem 'reverse_markdown' group :production do # we dont want request timing out in development while using byebug gem 'rack-timeout' + # for heroku autoscaling + gem 'judoscale-rails', require: false + gem 'judoscale-sidekiq', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index fff84d3a8..e2f5c91be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -397,6 +397,13 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) + judoscale-rails (1.8.2) + judoscale-ruby (= 1.8.2) + railties + judoscale-ruby (1.8.2) + judoscale-sidekiq (1.8.2) + judoscale-ruby (= 1.8.2) + sidekiq (>= 5.0) jwt (2.8.1) base64 kaminari (1.2.2) @@ -633,8 +640,7 @@ GEM retriable (3.1.2) reverse_markdown (2.1.1) nokogiri - rexml (3.3.6) - strscan + rexml (3.3.9) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.2) @@ -764,7 +770,6 @@ GEM stackprof (0.2.25) statsd-ruby (1.5.0) stripe (8.5.0) - strscan (3.1.0) telephone_number (1.4.20) test-prof (1.2.1) thor (1.3.1) @@ -893,6 +898,8 @@ DEPENDENCIES jbuilder json_refs json_schemer + judoscale-rails + judoscale-sidekiq jwt kaminari koala diff --git a/app/controllers/api/v1/accounts/articles_controller.rb b/app/controllers/api/v1/accounts/articles_controller.rb index d164778ac..9148e4386 100644 --- a/app/controllers/api/v1/accounts/articles_controller.rb +++ b/app/controllers/api/v1/accounts/articles_controller.rb @@ -6,13 +6,15 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController def index @portal_articles = @portal.articles - @all_articles = @portal_articles.search(list_params) - @articles_count = @all_articles.count + + set_article_count + + @articles = @articles.search(list_params) @articles = if list_params[:category_slug].present? - @all_articles.order_by_position.page(@current_page) + @articles.order_by_position.page(@current_page) else - @all_articles.order_by_updated_at.page(@current_page) + @articles.order_by_updated_at.page(@current_page) end end @@ -43,6 +45,19 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController private + def set_article_count + # Search the params without status and author_id, use this to + # compute mine count published draft etc + base_search_params = list_params.except(:status, :author_id) + @articles = @portal_articles.search(base_search_params) + + @articles_count = @articles.count + @mine_articles_count = @articles.search_by_author(Current.user.id).count + @published_articles_count = @articles.published.count + @draft_articles_count = @articles.draft.count + @archived_articles_count = @articles.archived.count + end + def fetch_article @article = @portal.articles.find(params[:id]) end @@ -53,9 +68,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController def article_params params.require(:article).permit( - :title, :slug, :position, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title, - :description, - { tags: [] }] + :title, :slug, :position, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, + :locale, meta: [:title, + :description, + { tags: [] }] ) end diff --git a/app/controllers/api/v1/accounts/integrations/captain_controller.rb b/app/controllers/api/v1/accounts/integrations/captain_controller.rb index 7df0e61df..15e81b726 100644 --- a/app/controllers/api/v1/accounts/integrations/captain_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/captain_controller.rb @@ -1,22 +1,53 @@ class Api::V1::Accounts::Integrations::CaptainController < Api::V1::Accounts::BaseController - before_action :check_admin_authorization? - before_action :fetch_hook + before_action :hook - def sso_url - params_string = - "token=#{URI.encode_www_form_component(@hook['settings']['access_token'])}" \ - "&email=#{URI.encode_www_form_component(@hook['settings']['account_email'])}" \ - "&account_id=#{URI.encode_www_form_component(@hook['settings']['account_id'])}" - - installation_config = InstallationConfig.find_by(name: 'CAPTAIN_APP_URL') - - sso_url = "#{installation_config.value}/sso?#{params_string}" - render json: { sso_url: sso_url }, status: :ok + def proxy + response = HTTParty.send(request_method, request_url, body: permitted_params[:body].to_json, headers: headers) + render plain: response.body, status: response.code end private - def fetch_hook - @hook = Current.account.hooks.find_by!(app_id: 'captain') + def headers + { + 'X-User-Email' => hook.settings['account_email'], + 'X-User-Token' => hook.settings['access_token'], + 'Content-Type' => 'application/json', + 'Accept' => '*/*' + } + end + + def request_path + request_route = with_leading_hash_on_route(params[:route]) + + return 'api/sessions/profile' if request_route == '/sessions/profile' + + "api/accounts/#{hook.settings['account_id']}#{request_route}" + end + + def request_url + base_url = InstallationConfig.find_by(name: 'CAPTAIN_API_URL').value + URI.join(base_url, request_path).to_s + end + + def hook + @hook ||= Current.account.hooks.find_by!(app_id: 'captain') + end + + def request_method + method = permitted_params[:method].downcase + raise 'Invalid or missing HTTP method' unless %w[get post put patch delete options head].include?(method) + + method + end + + def with_leading_hash_on_route(request_route) + return '' if request_route.blank? + + request_route.start_with?('/') ? request_route : "/#{request_route}" + end + + def permitted_params + params.permit(:method, :route, body: {}) end end diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index bd8da5549..fe9a03ef5 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -20,7 +20,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController end def create - @portal = Current.account.portals.build(portal_params) + @portal = Current.account.portals.build(portal_params.merge(live_chat_widget_params)) @portal.custom_domain = parsed_custom_domain @portal.save! process_attached_logo @@ -28,7 +28,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController def update ActiveRecord::Base.transaction do - @portal.update!(portal_params) if params[:portal].present? + @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 @@ -70,11 +70,21 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController def portal_params params.require(:portal).permit( - :account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug, :archived, { config: [:default_locale, - { allowed_locales: [] }] } + :account_id, :color, :custom_domain, :header_text, :homepage_link, + :name, :page_title, :slug, :archived, { config: [:default_locale, { allowed_locales: [] }] } ) end + def live_chat_widget_params + permitted_params = params.permit(:inbox_id) + return {} if permitted_params[:inbox_id].blank? + + inbox = Inbox.find(permitted_params[:inbox_id]) + return {} unless inbox.web_widget? + + { channel_web_widget_id: inbox.channel.id } + end + def portal_member_params params.require(:portal).permit(:account_id, member_ids: []) end diff --git a/app/controllers/public/api/v1/portals/articles_controller.rb b/app/controllers/public/api/v1/portals/articles_controller.rb index 2807ae41a..08c62bada 100644 --- a/app/controllers/public/api/v1/portals/articles_controller.rb +++ b/app/controllers/public/api/v1/portals/articles_controller.rb @@ -30,7 +30,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B def set_article @article = @portal.articles.find_by(slug: permitted_params[:article_slug]) - @article.increment_view_count + @article.increment_view_count if @article.published? @parsed_content = render_article_content(@article.content) end diff --git a/app/javascript/dashboard/api/helpCenter/articles.js b/app/javascript/dashboard/api/helpCenter/articles.js index e5847dc1b..727340ed5 100644 --- a/app/javascript/dashboard/api/helpCenter/articles.js +++ b/app/javascript/dashboard/api/helpCenter/articles.js @@ -52,12 +52,13 @@ class ArticlesAPI extends PortalsAPI { } createArticle({ portalSlug, articleObj }) { - const { content, title, author_id, category_id } = articleObj; + const { content, title, authorId, categoryId, locale } = articleObj; return axios.post(`${this.url}/${portalSlug}/articles`, { content, title, - author_id, - category_id, + author_id: authorId, + category_id: categoryId, + locale, }); } diff --git a/app/javascript/dashboard/api/integrations.js b/app/javascript/dashboard/api/integrations.js index 975857ce1..59a3e244a 100644 --- a/app/javascript/dashboard/api/integrations.js +++ b/app/javascript/dashboard/api/integrations.js @@ -33,8 +33,8 @@ class IntegrationsAPI extends ApiClient { return axios.delete(`${this.baseUrl()}/integrations/hooks/${hookId}`); } - fetchCaptainURL() { - return axios.get(`${this.baseUrl()}/integrations/captain/sso_url`); + requestCaptain(body) { + return axios.post(`${this.baseUrl()}/integrations/captain/proxy`, body); } } diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss index 4f59b592c..e3474e616 100644 --- a/app/javascript/dashboard/assets/scss/_woot.scss +++ b/app/javascript/dashboard/assets/scss/_woot.scss @@ -46,6 +46,14 @@ @apply hidden; } +.n-blue-border { + @apply border-n-blue-border; +} + +.n-blue-text { + @apply text-n-blue-text; +} + // scss-lint:disable PropertySortOrder @layer base { /* NEXT COLORS START */ @@ -103,24 +111,25 @@ --teal-11: 0 133 115; --teal-12: 13 61 56; - --background-color: 248 248 248; - --solid-1: 255 255 255; - --solid-2: 252 252 252; - --solid-3: 255 255 255; - --solid-active: 250 251 251; - --white-alpha: 255 255 255 0.1; - --border-weak: 231 231 231; + --background-color: 253 253 253; + --text-blue: 8 109 224; + --border-container: 236 236 236; --border-strong: 235 235 235; - --amber-solid: 252 232 193; - --blue-solid: 218 236 255; - --blue: 39 129 246; + --border-weak: 234 234 234; + --solid-1: 255 255 255; + --solid-2: 255 255 255; + --solid-3: 255 255 255; + --solid-active: 255 255 255; + --solid-amber: 252 232 193; + --solid-blue: 218 236 255; - /* alpha is added by default */ - --alpha-1: 36, 38, 48, 0.06; - --alpha-2: 130, 134, 150, 0.12; - --alpha-3: 255, 255, 255, 0.9; + --alpha-1: 67, 67, 67, 0.06; + --alpha-2: 201, 202, 207, 0.15; + --alpha-3: 255, 255, 255, 0.96; --black-alpha-1: 0, 0, 0, 0.12; --black-alpha-2: 0, 0, 0, 0.04; + --border-blue: 39, 129, 246, 0.5; + --white-alpha: 255, 255, 255, 0.1; } body.dark { @@ -178,22 +187,23 @@ --teal-12: 173 240 221; --background-color: 18 18 19; + --border-strong: 52 52 52; + --border-weak: 38 38 42; --solid-1: 23 23 26; --solid-2: 29 30 36; - --solid-3: 36 38 48; - --solid-active: 51 53 64; - --border-weak: 34 34 37; - --border-strong: 46 47 49; - --amber-solid: 42 37 30; - --blue-solid: 16 49 91; - --blue: 126 182 255; + --solid-3: 44 45 54; + --solid-active: 53 57 66; + --solid-amber: 42 37 30; + --solid-blue: 16 49 91; + --text-blue: 126 182 255; - /* alpha is added by default */ - --alpha-1: 35, 37, 45, 0.8; - --alpha-2: 130, 134, 150, 0.15; - --alpha-3: 32, 33, 37, 0.9; + --alpha-1: 36, 36, 36, 0.8; + --alpha-2: 139, 147, 182, 0.15; + --alpha-3: 36, 38, 45, 0.9; --black-alpha-1: 0, 0, 0, 0.3; --black-alpha-2: 0, 0, 0, 0.2; + --border-blue: 39, 129, 246, 0.5; + --border-container: 236, 236, 236, 0; --white-alpha: 255, 255, 255, 0.1; } /* NEXT COLORS END */ @@ -540,3 +550,15 @@ --color-orange-900: 255 224 194; } } + +@layer utilities { + /* Hide scrollbar for Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} diff --git a/app/javascript/dashboard/assets/scss/widgets/_base.scss b/app/javascript/dashboard/assets/scss/widgets/_base.scss index d5d2e227f..d35c1cfc9 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_base.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_base.scss @@ -31,7 +31,13 @@ hr { ul, ol, dl { - @apply mb-2 list-disc list-outside leading-[1.65]; + @apply list-disc list-outside leading-[1.65]; +} + +ul:not(.reset-base), +ol:not(.reset-base), +dl:not(.reset-base) { + @apply mb-0; } // Form elements diff --git a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss b/app/javascript/dashboard/assets/scss/widgets/_buttons.scss index 587b810ca..640e1395f 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_buttons.scss @@ -96,7 +96,7 @@ button { } // @TODDO - Remove after moving all buttons to woot-button - .icon+.button__content { + .icon + .button__content { @apply w-auto; } diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue new file mode 100644 index 000000000..dbdbb5b45 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue @@ -0,0 +1,141 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue new file mode 100644 index 000000000..6f8c548d4 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue @@ -0,0 +1,54 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/SMSCampaignDetails.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/SMSCampaignDetails.vue new file mode 100644 index 000000000..0c54c43eb --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/SMSCampaignDetails.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue new file mode 100644 index 000000000..11006bfe3 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignLayout.vue @@ -0,0 +1,52 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/EmptyState/CampaignEmptyStateContent.js b/app/javascript/dashboard/components-next/Campaigns/EmptyState/CampaignEmptyStateContent.js new file mode 100644 index 000000000..4f409196f --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/EmptyState/CampaignEmptyStateContent.js @@ -0,0 +1,212 @@ +export const ONGOING_CAMPAIGN_EMPTY_STATE_CONTENT = [ + { + id: 1, + title: 'Chatbot Assistance', + inbox: { + id: 2, + name: 'PaperLayer Website', + channel_type: 'Channel::WebWidget', + phone_number: '', + }, + sender: { + id: 1, + name: 'Alexa Rivera', + }, + message: 'Hello! ๐Ÿ‘‹ Need help with our chatbot features? Feel free to ask!', + campaign_status: 'active', + enabled: true, + campaign_type: 'ongoing', + trigger_rules: { + url: 'https://www.chatwoot.com/features/chatbot/', + time_on_page: 10, + }, + trigger_only_during_business_hours: true, + created_at: '2024-10-24T13:10:26.636Z', + updated_at: '2024-10-24T13:10:26.636Z', + }, + { + id: 2, + title: 'Pricing Information Support', + inbox: { + id: 2, + name: 'PaperLayer Website', + channel_type: 'Channel::WebWidget', + phone_number: '', + }, + sender: { + id: 1, + name: 'Jamie Lee', + }, + message: 'Hello! ๐Ÿ‘‹ Any questions on pricing? Iโ€™m here to help!', + campaign_status: 'active', + enabled: false, + campaign_type: 'ongoing', + trigger_rules: { + url: 'https://www.chatwoot.com/pricings', + time_on_page: 10, + }, + trigger_only_during_business_hours: false, + created_at: '2024-10-24T13:11:08.763Z', + updated_at: '2024-10-24T13:11:08.763Z', + }, + { + id: 3, + title: 'Product Setup Assistance', + inbox: { + id: 2, + name: 'PaperLayer Website', + channel_type: 'Channel::WebWidget', + phone_number: '', + }, + sender: { + id: 1, + name: 'Chatwoot', + }, + message: 'Hi! Chatwoot here. Need help setting up? Let me know!', + campaign_status: 'active', + enabled: false, + campaign_type: 'ongoing', + trigger_rules: { + url: 'https://{*.}?chatwoot.com/apps/account/*/settings/inboxes/new/', + time_on_page: 10, + }, + trigger_only_during_business_hours: false, + created_at: '2024-10-24T13:11:44.285Z', + updated_at: '2024-10-24T13:11:44.285Z', + }, + { + id: 4, + title: 'General Assistance Campaign', + inbox: { + id: 2, + name: 'PaperLayer Website', + channel_type: 'Channel::WebWidget', + phone_number: '', + }, + sender: { + id: 1, + name: 'Chris Barlow', + }, + message: + 'Hi there! ๐Ÿ‘‹ Iโ€™m here for any questions you may have. Letโ€™s chat!', + campaign_status: 'active', + enabled: true, + campaign_type: 'ongoing', + trigger_rules: { + url: 'https://siv.com', + time_on_page: 200, + }, + trigger_only_during_business_hours: false, + created_at: '2024-10-29T19:54:33.741Z', + updated_at: '2024-10-29T19:56:26.296Z', + }, +]; + +export const ONE_OFF_CAMPAIGN_EMPTY_STATE_CONTENT = [ + { + id: 1, + title: 'Customer Feedback Request', + inbox: { + id: 6, + name: 'PaperLayer Mobile', + channel_type: 'Channel::Sms', + phone_number: '+29818373149903', + provider: 'default', + }, + message: + 'Hello! Enjoying our product? Share your feedback on G2 and earn a $25 Amazon coupon: https://chwt.app/g2-review', + campaign_status: 'active', + enabled: true, + campaign_type: 'one_off', + scheduled_at: 1729775588, + audience: [ + { id: 4, type: 'Label' }, + { id: 5, type: 'Label' }, + { id: 6, type: 'Label' }, + ], + trigger_rules: {}, + trigger_only_during_business_hours: false, + created_at: '2024-10-24T13:13:08.496Z', + updated_at: '2024-10-24T13:15:38.698Z', + }, + { + id: 2, + title: 'Welcome New Customer', + inbox: { + id: 6, + name: 'PaperLayer Mobile', + channel_type: 'Channel::Sms', + phone_number: '+29818373149903', + provider: 'default', + }, + message: 'Welcome aboard! ๐ŸŽ‰ Let us know if you have any questions.', + campaign_status: 'completed', + enabled: true, + campaign_type: 'one_off', + scheduled_at: 1729732500, + audience: [ + { id: 1, type: 'Label' }, + { id: 6, type: 'Label' }, + { id: 5, type: 'Label' }, + { id: 2, type: 'Label' }, + { id: 4, type: 'Label' }, + ], + trigger_rules: {}, + trigger_only_during_business_hours: false, + created_at: '2024-10-24T13:14:00.168Z', + updated_at: '2024-10-24T13:15:38.707Z', + }, + { + id: 3, + title: 'New Business Welcome', + inbox: { + id: 6, + name: 'PaperLayer Mobile', + channel_type: 'Channel::Sms', + phone_number: '+29818373149903', + provider: 'default', + }, + message: 'Hello! Weโ€™re excited to have your business with us!', + campaign_status: 'active', + enabled: true, + campaign_type: 'one_off', + scheduled_at: 1730368440, + audience: [ + { id: 1, type: 'Label' }, + { id: 3, type: 'Label' }, + { id: 6, type: 'Label' }, + { id: 4, type: 'Label' }, + { id: 2, type: 'Label' }, + { id: 5, type: 'Label' }, + ], + trigger_rules: {}, + trigger_only_during_business_hours: false, + created_at: '2024-10-30T07:54:49.915Z', + updated_at: '2024-10-30T07:54:49.915Z', + }, + { + id: 4, + title: 'New Member Onboarding', + inbox: { + id: 6, + name: 'PaperLayer Mobile', + channel_type: 'Channel::Sms', + phone_number: '+29818373149903', + provider: 'default', + }, + message: 'Welcome to the team! Reach out if you have questions.', + campaign_status: 'completed', + enabled: true, + campaign_type: 'one_off', + scheduled_at: 1730304840, + audience: [ + { id: 1, type: 'Label' }, + { id: 3, type: 'Label' }, + { id: 6, type: 'Label' }, + ], + trigger_rules: {}, + trigger_only_during_business_hours: false, + created_at: '2024-10-29T16:14:10.374Z', + updated_at: '2024-10-30T16:15:03.157Z', + }, +]; diff --git a/app/javascript/dashboard/components-next/Campaigns/EmptyState/LiveChatCampaignEmptyState.vue b/app/javascript/dashboard/components-next/Campaigns/EmptyState/LiveChatCampaignEmptyState.vue new file mode 100644 index 000000000..4461a7da7 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/EmptyState/LiveChatCampaignEmptyState.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/EmptyState/SMSCampaignEmptyState.vue b/app/javascript/dashboard/components-next/Campaigns/EmptyState/SMSCampaignEmptyState.vue new file mode 100644 index 000000000..d0ed60abe --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/EmptyState/SMSCampaignEmptyState.vue @@ -0,0 +1,38 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/CampaignList.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/CampaignList.vue new file mode 100644 index 000000000..80e7437a6 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/CampaignList.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/ConfirmDeleteCampaignDialog.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/ConfirmDeleteCampaignDialog.vue new file mode 100644 index 000000000..f55c88e46 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/ConfirmDeleteCampaignDialog.vue @@ -0,0 +1,49 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/EditLiveChatCampaignDialog.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/EditLiveChatCampaignDialog.vue new file mode 100644 index 000000000..458ee1f1c --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/EditLiveChatCampaignDialog.vue @@ -0,0 +1,76 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignDialog.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignDialog.vue new file mode 100644 index 000000000..2c4b9848c --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignDialog.vue @@ -0,0 +1,54 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue new file mode 100644 index 000000000..1a9bc8cdf --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/LiveChatCampaign/LiveChatCampaignForm.vue @@ -0,0 +1,323 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignDialog.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignDialog.vue new file mode 100644 index 000000000..b190b031b --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignDialog.vue @@ -0,0 +1,49 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue new file mode 100644 index 000000000..653e58a9a --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/SMSCampaign/SMSCampaignForm.vue @@ -0,0 +1,189 @@ + + +