diff --git a/Gemfile b/Gemfile index a5068e765..c4989c538 100644 --- a/Gemfile +++ b/Gemfile @@ -84,6 +84,7 @@ gem 'barnes' gem 'devise', '>= 4.9.4' gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot' gem 'devise_token_auth', '>= 1.2.3' +gem 'rails-i18n', '~> 7.0' # two-factor authentication gem 'devise-two-factor', '>= 5.0.0' # authorization diff --git a/Gemfile.lock b/Gemfile.lock index b77e5880f..7d29e0b02 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -727,6 +727,9 @@ GEM rails-html-sanitizer (1.6.1) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + rails-i18n (7.0.10) + i18n (>= 0.7, < 2) + railties (>= 6.0.0, < 8) railties (7.1.5.2) actionpack (= 7.1.5.2) activesupport (= 7.1.5.2) @@ -1125,6 +1128,7 @@ DEPENDENCIES rack-mini-profiler (>= 3.2.0) rack-timeout rails (~> 7.1) + rails-i18n (~> 7.0) redis redis-namespace responders (>= 3.1.1) diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index 532487a1b..5127ea612 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -44,7 +44,11 @@ class AccountBuilder end def create_account - @account = Account.create!(name: account_name, locale: I18n.locale) + @account = Account.create!( + name: account_name, + locale: I18n.locale, + custom_attributes: { 'onboarding_step' => 'account_details' } + ) Current.account = @account end diff --git a/app/builders/agent_builder.rb b/app/builders/agent_builder.rb index 2fe11cae0..d2715011c 100644 --- a/app/builders/agent_builder.rb +++ b/app/builders/agent_builder.rb @@ -29,8 +29,9 @@ class AgentBuilder user = User.from_email(email) return user if user + @name = email.split('@').first if @name.blank? temp_password = "1!aA#{SecureRandom.alphanumeric(12)}" - User.create!(email: email, name: name, password: temp_password, password_confirmation: temp_password) + User.create!(email: email, name: @name, password: temp_password, password_confirmation: temp_password) end # Checks if the user needs confirmation. diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index dd5346bd6..eafda0fe2 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -5,7 +5,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController sort_on :phone_number, type: :string sort_on :last_activity_at, internal_name: :order_on_last_activity_at, type: :scope, scope_params: [:direction] sort_on :created_at, internal_name: :order_on_created_at, type: :scope, scope_params: [:direction] - sort_on :company, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction] + sort_on :company_name, internal_name: :order_on_company_name, type: :scope, scope_params: [:direction] sort_on :city, internal_name: :order_on_city, type: :scope, scope_params: [:direction] sort_on :country, internal_name: :order_on_country_name, type: :scope, scope_params: [:direction] diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index 972b244fa..ade83d8ec 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -61,9 +61,8 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController end def process_attached_logo - blob_id = params[:blob_id] - blob = ActiveStorage::Blob.find_signed(blob_id) - @portal.logo.attach(blob) + blob = ActiveStorage::Blob.find_signed(params[:blob_id].to_s) + @portal.logo.attach(blob) if blob end private diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 7176d6e1b..dddf3dd09 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -58,6 +58,7 @@ class Api::V1::AccountsController < Api::BaseController @account.assign_attributes(account_params.slice(:name, :locale, :domain, :support_email)) @account.custom_attributes.merge!(custom_attributes_params) @account.settings.merge!(settings_params) + @account.custom_attributes.delete('onboarding_step') if @account.custom_attributes['onboarding_step'] == 'account_details' @account.custom_attributes['onboarding_step'] = 'invite_team' if @account.custom_attributes['onboarding_step'] == 'account_update' @account.save! end @@ -71,9 +72,10 @@ class Api::V1::AccountsController < Api::BaseController private def enqueue_branding_enrichment - return if account_params[:email].blank? + email = account_params[:email].presence || @user&.email + return if email.blank? - Account::BrandingEnrichmentJob.perform_later(@account.id, account_params[:email]) + Account::BrandingEnrichmentJob.perform_later(@account.id, email) Redis::Alfred.set(format(Redis::Alfred::ACCOUNT_ONBOARDING_ENRICHMENT, account_id: @account.id), '1', ex: 30) rescue StandardError => e # Enrichment is optional — never let queue/Redis failures abort signup @@ -109,7 +111,7 @@ class Api::V1::AccountsController < Api::BaseController end def custom_attributes_params - params.permit(:industry, :company_size, :timezone) + params.permit(:industry, :company_size, :timezone, :referral_source, :user_role) end def settings_params diff --git a/app/controllers/api/v1/profile/mfa_controller.rb b/app/controllers/api/v1/profile/mfa_controller.rb index dd874f222..8480b64fb 100644 --- a/app/controllers/api/v1/profile/mfa_controller.rb +++ b/app/controllers/api/v1/profile/mfa_controller.rb @@ -2,8 +2,8 @@ class Api::V1::Profile::MfaController < Api::BaseController before_action :check_mfa_feature_available before_action :check_mfa_enabled, only: [:destroy, :backup_codes] before_action :check_mfa_disabled, only: [:create, :verify] - before_action :validate_otp, only: [:verify, :backup_codes, :destroy] before_action :validate_password, only: [:destroy] + before_action :validate_otp, only: [:verify, :backup_codes, :destroy] def show; end @@ -48,7 +48,8 @@ class Api::V1::Profile::MfaController < Api::BaseController def validate_otp authenticated = Mfa::AuthenticationService.new( user: current_user, - otp_code: mfa_params[:otp_code] + otp_code: mfa_params[:otp_code], + backup_code: mfa_params[:backup_code] ).authenticate return if authenticated @@ -63,6 +64,6 @@ class Api::V1::Profile::MfaController < Api::BaseController end def mfa_params - params.permit(:otp_code, :password) + params.permit(:otp_code, :backup_code, :password) end end diff --git a/app/controllers/platform/api/v1/agent_bots_controller.rb b/app/controllers/platform/api/v1/agent_bots_controller.rb index dd70a1ba5..594bb856e 100644 --- a/app/controllers/platform/api/v1/agent_bots_controller.rb +++ b/app/controllers/platform/api/v1/agent_bots_controller.rb @@ -3,7 +3,7 @@ class Platform::Api::V1::AgentBotsController < PlatformController before_action :validate_platform_app_permissible, except: [:index, :create] def index - @resources = @platform_app.platform_app_permissibles.where(permissible_type: 'AgentBot').all + @resources = @platform_app.platform_app_permissibles.where(permissible_type: 'AgentBot').includes(:permissible) end def show; end diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb index fcc8b463d..67e8d953d 100644 --- a/app/helpers/email_helper.rb +++ b/app/helpers/email_helper.rb @@ -7,7 +7,7 @@ module EmailHelper def render_email_html(content) return '' if content.blank? - ChatwootMarkdownRenderer.new(content).render_message.to_s + ChatwootMarkdownRenderer.new(content).render_message(hardbreaks: true).to_s end # Raise a standard error if any email address is invalid diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index a706e2df5..988829407 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -59,7 +59,6 @@ export default { isRTL: 'accounts/isRTL', currentUser: 'getCurrentUser', authUIFlags: 'getAuthUIFlags', - accountUIFlags: 'accounts/getUIFlags', }), hideOnOnboardingView() { return !isOnOnboardingView(this.$route); @@ -107,8 +106,9 @@ export default { this.$store.dispatch('setActiveAccount', { accountId: this.currentAccountId, }); + const account = this.getAccount(this.currentAccountId); const { locale, latest_chatwoot_version: latestChatwootVersion } = - this.getAccount(this.currentAccountId); + account; const { pubsub_token: pubsubToken } = this.currentUser || {}; // If user locale is set, use it; otherwise use account locale this.setLocale(this.uiSettings?.locale || locale); @@ -131,7 +131,7 @@ export default { diff --git a/app/javascript/dashboard/components-next/filter/contactProvider.js b/app/javascript/dashboard/components-next/filter/contactProvider.js index a39000817..79933c138 100644 --- a/app/javascript/dashboard/components-next/filter/contactProvider.js +++ b/app/javascript/dashboard/components-next/filter/contactProvider.js @@ -135,6 +135,16 @@ export function useContactFilterContext() { filterOperators: containmentOperators.value, attributeModel: 'standard', }, + { + attributeKey: CONTACT_ATTRIBUTES.COMPANY_NAME, + value: CONTACT_ATTRIBUTES.COMPANY_NAME, + attributeName: t('CONTACTS_LAYOUT.FILTER.COMPANY'), + label: t('CONTACTS_LAYOUT.FILTER.COMPANY'), + inputType: 'plainText', + dataType: 'text', + filterOperators: containmentOperators.value, + attributeModel: 'standard', + }, { attributeKey: CONTACT_ATTRIBUTES.CREATED_AT, value: CONTACT_ATTRIBUTES.CREATED_AT, diff --git a/app/javascript/dashboard/components-next/filter/helper/filterHelper.js b/app/javascript/dashboard/components-next/filter/helper/filterHelper.js index 274eecb49..ba0dd24fa 100644 --- a/app/javascript/dashboard/components-next/filter/helper/filterHelper.js +++ b/app/javascript/dashboard/components-next/filter/helper/filterHelper.js @@ -23,6 +23,7 @@ export const CONTACT_ATTRIBUTES = { IDENTIFIER: 'identifier', COUNTRY_CODE: 'country_code', CITY: 'city', + COMPANY_NAME: 'company_name', CREATED_AT: 'created_at', LAST_ACTIVITY_AT: 'last_activity_at', REFERER: 'referer', diff --git a/app/javascript/dashboard/components-next/inline-input/InlineInput.vue b/app/javascript/dashboard/components-next/inline-input/InlineInput.vue index d65bb4257..9a9960c97 100644 --- a/app/javascript/dashboard/components-next/inline-input/InlineInput.vue +++ b/app/javascript/dashboard/components-next/inline-input/InlineInput.vue @@ -30,6 +30,10 @@ const props = defineProps({ type: Boolean, default: false, }, + readonly: { + type: Boolean, + default: false, + }, focusOnMount: { type: Boolean, default: false, @@ -82,6 +86,7 @@ onMounted(() => { defineExpose({ focus: () => inlineInputRef.value?.focus(), + blur: () => inlineInputRef.value?.blur(), }); @@ -106,6 +111,7 @@ defineExpose({ :type="type" :placeholder="placeholder" :disabled="disabled" + :readonly="readonly" :class="customInputClass" class="flex w-full reset-base text-sm h-6 !mb-0 border-0 rounded-none outline-none outline-0 bg-transparent dark:bg-transparent placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 disabled:cursor-not-allowed disabled:opacity-50 text-n-slate-12 dark:text-n-slate-12 transition-all duration-500 ease-in-out" @input="handleInput" diff --git a/app/javascript/dashboard/helper/AnalyticsHelper/events.js b/app/javascript/dashboard/helper/AnalyticsHelper/events.js index 97b7931fb..6dc47cfbf 100644 --- a/app/javascript/dashboard/helper/AnalyticsHelper/events.js +++ b/app/javascript/dashboard/helper/AnalyticsHelper/events.js @@ -153,3 +153,8 @@ export const YEAR_IN_REVIEW_EVENTS = Object.freeze({ NEXT_CLICKED: 'Year in Review: Next clicked', SHARE_CLICKED: 'Year in Review: Share clicked', }); + +export const ONBOARDING_EVENTS = Object.freeze({ + ACCOUNT_DETAILS_VISITED: 'Onboarding: Account details visited', + ACCOUNT_DETAILS_COMPLETED: 'Onboarding: Account details completed', +}); diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js index 991576e66..6feebb35d 100644 --- a/app/javascript/dashboard/helper/actionCable.js +++ b/app/javascript/dashboard/helper/actionCable.js @@ -33,6 +33,7 @@ class ActionCableConnector extends BaseActionCableConnector { 'conversation.read': this.onConversationRead, 'conversation.updated': this.onConversationUpdated, 'account.cache_invalidated': this.onCacheInvalidate, + 'account.enrichment_completed': this.onEnrichmentCompleted, 'copilot.message.created': this.onCopilotMessageCreated, }; } @@ -194,6 +195,10 @@ class ActionCableConnector extends BaseActionCableConnector { this.app.$store.dispatch('copilotMessages/upsert', data); }; + onEnrichmentCompleted = () => { + this.app.$store.dispatch('accounts/get', { silent: true }); + }; + onCacheInvalidate = data => { const keys = data.cache_keys; this.app.$store.dispatch('labels/revalidate', { newKey: keys.label }); diff --git a/app/javascript/dashboard/i18n/locale/en/automation.json b/app/javascript/dashboard/i18n/locale/en/automation.json index e96a28b40..2c4852dc8 100644 --- a/app/javascript/dashboard/i18n/locale/en/automation.json +++ b/app/javascript/dashboard/i18n/locale/en/automation.json @@ -182,6 +182,7 @@ "BROWSER_LANGUAGE": "Browser Language", "MAIL_SUBJECT": "Email Subject", "COUNTRY_NAME": "Country", + "COMPANY_NAME": "Company", "REFERER_LINK": "Referrer Link", "ASSIGNEE_NAME": "Assignee", "TEAM_NAME": "Team", diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json index 9234071d1..1c69c4b29 100644 --- a/app/javascript/dashboard/i18n/locale/en/contact.json +++ b/app/javascript/dashboard/i18n/locale/en/contact.json @@ -387,6 +387,7 @@ "IDENTIFIER": "Identifier", "COUNTRY": "Country", "CITY": "City", + "COMPANY": "Company", "CREATED_AT": "Created at", "LAST_ACTIVITY": "Last activity", "REFERER_LINK": "Referer link", diff --git a/app/javascript/dashboard/i18n/locale/en/index.js b/app/javascript/dashboard/i18n/locale/en/index.js index 261ca85e6..31486a247 100644 --- a/app/javascript/dashboard/i18n/locale/en/index.js +++ b/app/javascript/dashboard/i18n/locale/en/index.js @@ -39,6 +39,7 @@ import teamsSettings from './teamsSettings.json'; import whatsappTemplates from './whatsappTemplates.json'; import contentTemplates from './contentTemplates.json'; import mfa from './mfa.json'; +import onboarding from './onboarding.json'; import yearInReview from './yearInReview.json'; export default { @@ -83,5 +84,6 @@ export default { ...whatsappTemplates, ...contentTemplates, ...mfa, + ...onboarding, ...yearInReview, }; diff --git a/app/javascript/dashboard/i18n/locale/en/mfa.json b/app/javascript/dashboard/i18n/locale/en/mfa.json index b03917bcd..8e356aad4 100644 --- a/app/javascript/dashboard/i18n/locale/en/mfa.json +++ b/app/javascript/dashboard/i18n/locale/en/mfa.json @@ -51,10 +51,14 @@ }, "DISABLE": { "TITLE": "Disable Two-Factor Authentication", - "DESCRIPTION": "You'll need to enter your password and a verification code to disable two-factor authentication.", + "DESCRIPTION": "You'll need to enter your password and either a verification code from your authenticator app or a backup code to disable two-factor authentication.", "PASSWORD": "Password", "OTP_CODE": "Verification Code", "OTP_CODE_PLACEHOLDER": "000000", + "BACKUP_CODE": "Backup Code", + "BACKUP_CODE_PLACEHOLDER": "Enter one of your backup codes", + "USE_BACKUP_CODE": "Lost access to your authenticator? Use a backup code instead", + "USE_OTP_CODE": "Use a verification code from your authenticator app", "CONFIRM": "Disable 2FA", "CANCEL": "Cancel", "SUCCESS": "Two-factor authentication has been disabled", diff --git a/app/javascript/dashboard/i18n/locale/en/onboarding.json b/app/javascript/dashboard/i18n/locale/en/onboarding.json new file mode 100644 index 000000000..d7c960002 --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/en/onboarding.json @@ -0,0 +1,34 @@ +{ + "ONBOARDING_NEXT": { + "GREETING": "Hello {name}!", + "SUBTITLE": "Please review the following details", + "YOUR_DETAILS": "Your details", + "COMPANY_DETAILS": "Company details", + "FIELDS": { + "EMAIL": "Email", + "YOUR_ROLE": "Your Role", + "WEBSITE": "Website", + "LANGUAGE": "Language", + "TIMEZONE": "Timezone", + "COMPANY_SIZE": "Company Size", + "INDUSTRY": "Industry", + "REFERRAL_SOURCE": "Where did you find us?" + }, + "PLACEHOLDERS": { + "SELECT_ROLE": "Select your role", + "ENTER_WEBSITE": "www.example.com", + "SELECT_LANGUAGE": "Select language", + "SELECT_TIMEZONE": "Select timezone", + "SELECT_COMPANY_SIZE": "Select company size", + "SELECT_INDUSTRY": "Select industry", + "SELECT_REFERRAL_SOURCE": "Select source" + }, + "EMAIL_VERIFIED": "Email verified", + "SETTING_UP": "Setting up your account...", + "CONTINUE": "Continue", + "SAVING": "Saving...", + "VALIDATION_ERROR": "Please fill in all required fields", + "SUCCESS": "Details saved successfully", + "ERROR": "Could not save details. Please try again." + } +} diff --git a/app/javascript/dashboard/modules/search/components/SearchResultContactItem.vue b/app/javascript/dashboard/modules/search/components/SearchResultContactItem.vue index a8ce1220b..485306797 100644 --- a/app/javascript/dashboard/modules/search/components/SearchResultContactItem.vue +++ b/app/javascript/dashboard/modules/search/components/SearchResultContactItem.vue @@ -49,7 +49,6 @@ const navigateTo = computed(() => { const countriesMap = computed(() => { return countries.reduce((acc, country) => { - acc[country.code] = country; acc[country.id] = country; return acc; }, {}); diff --git a/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js b/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js index 8d67ccc68..c5ea28774 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js +++ b/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js @@ -53,6 +53,14 @@ const filterTypes = [ filterOperators: OPERATOR_TYPES_3, attribute_type: 'standard', }, + { + attributeKey: 'company_name', + attributeI18nKey: 'COMPANY', + inputType: 'plain_text', + dataType: 'text', + filterOperators: OPERATOR_TYPES_3, + attributeModel: 'standard', + }, { attributeKey: 'created_at', attributeI18nKey: 'CREATED_AT', @@ -124,6 +132,10 @@ export const filterAttributeGroups = [ key: 'city', i18nKey: 'CITY', }, + { + key: 'company_name', + i18nKey: 'COMPANY', + }, { key: 'created_at', i18nKey: 'CREATED_AT', diff --git a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js index 1882923bd..87bae7d11 100644 --- a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js +++ b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js @@ -12,6 +12,7 @@ import { routes as captainRoutes } from './captain/captain.routes'; import AppContainer from './Dashboard.vue'; import Suspended from './suspended/Index.vue'; import NoAccounts from './noAccounts/Index.vue'; +import OnboardingAccountDetails from './onboarding/Index.vue'; export default { routes: [ @@ -31,6 +32,14 @@ export default { ...campaignsRoutes.routes, ], }, + { + path: frontendURL('accounts/:accountId/onboarding'), + name: 'onboarding_account_details', + meta: { + permissions: ['administrator', 'agent', 'custom_role'], + }, + component: OnboardingAccountDetails, + }, { path: frontendURL('accounts/:accountId/suspended'), name: 'account_suspended', diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/Index.vue b/app/javascript/dashboard/routes/dashboard/onboarding/Index.vue new file mode 100644 index 000000000..66ac0d57f --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/Index.vue @@ -0,0 +1,417 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormRow.vue b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormRow.vue new file mode 100644 index 000000000..dbe3acd93 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormRow.vue @@ -0,0 +1,18 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormSelect.vue b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormSelect.vue new file mode 100644 index 000000000..a7264e55d --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingFormSelect.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingLayout.vue b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingLayout.vue new file mode 100644 index 000000000..63b3fa391 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingLayout.vue @@ -0,0 +1,120 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingSection.vue b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingSection.vue new file mode 100644 index 000000000..479aa4c81 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/OnboardingSection.vue @@ -0,0 +1,49 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/onboarding/constants.js b/app/javascript/dashboard/routes/dashboard/onboarding/constants.js new file mode 100644 index 000000000..b14255ccd --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/onboarding/constants.js @@ -0,0 +1,68 @@ +export const COMPANY_SIZE_OPTIONS = [ + { value: '1-10', label: '1 - 10' }, + { value: '11-50', label: '11 - 50' }, + { value: '51-200', label: '51 - 200' }, + { value: '201-500', label: '201 - 500' }, + { value: '500+', label: '500+' }, +]; + +export const INDUSTRY_OPTIONS = [ + { value: 'Aerospace & Defense', label: 'Aerospace & Defense' }, + { value: 'Agriculture & Food', label: 'Agriculture & Food' }, + { + value: 'Automotive & Transportation', + label: 'Automotive & Transportation', + }, + { value: 'Chemicals & Materials', label: 'Chemicals & Materials' }, + { + value: 'Construction & Built Environment', + label: 'Construction & Built Environment', + }, + { + value: 'Consumer Packaged Goods (CPG)', + label: 'Consumer Packaged Goods (CPG)', + }, + { value: 'Education', label: 'Education' }, + { value: 'Entertainment', label: 'Entertainment' }, + { value: 'Finance', label: 'Finance' }, + { value: 'Government & Nonprofit', label: 'Government & Nonprofit' }, + { value: 'Healthcare', label: 'Healthcare' }, + { value: 'Hospitality & Tourism', label: 'Hospitality & Tourism' }, + { value: 'Industrial & Energy', label: 'Industrial & Energy' }, + { value: 'Legal & Compliance', label: 'Legal & Compliance' }, + { value: 'Lifestyle & Leisure', label: 'Lifestyle & Leisure' }, + { value: 'Logistics & Supply Chain', label: 'Logistics & Supply Chain' }, + { value: 'Luxury & Fashion', label: 'Luxury & Fashion' }, + { value: 'News & Media', label: 'News & Media' }, + { + value: 'Professional Services & Agencies', + label: 'Professional Services & Agencies', + }, + { value: 'Real Estate & PropTech', label: 'Real Estate & PropTech' }, + { value: 'Retail & E-commerce', label: 'Retail & E-commerce' }, + { value: 'Sports', label: 'Sports' }, + { value: 'Technology', label: 'Technology' }, + { value: 'Telecommunications', label: 'Telecommunications' }, + { value: 'Other', label: 'Other' }, +]; + +export const REFERRAL_SOURCE_OPTIONS = [ + { value: 'google', label: 'Google' }, + { value: 'reddit', label: 'Reddit' }, + { value: 'twitter', label: 'Twitter/X' }, + { value: 'linkedin', label: 'LinkedIn' }, + { value: 'friend', label: 'Friend/Colleague' }, + { value: 'blog', label: 'Blog/Article' }, + { value: 'github', label: 'GitHub' }, + { value: 'other', label: 'Other' }, +]; + +export const USER_ROLE_OPTIONS = [ + { value: 'founder', label: 'Founder/CEO' }, + { value: 'product_manager', label: 'Product Manager' }, + { value: 'engineering', label: 'Engineering' }, + { value: 'support_lead', label: 'Support Lead' }, + { value: 'marketing', label: 'Marketing' }, + { value: 'sales', label: 'Sales' }, + { value: 'other', label: 'Other' }, +]; diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js index c7f4529b8..3c073ec7e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js @@ -74,6 +74,12 @@ export const AUTOMATIONS = { inputType: 'plain_text', filterOperators: OPERATOR_TYPES_6, }, + { + key: 'company_name', + name: 'COMPANY_NAME', + inputType: 'plain_text', + filterOperators: OPERATOR_TYPES_2, + }, { key: 'labels', name: 'LABELS', @@ -180,6 +186,12 @@ export const AUTOMATIONS = { inputType: 'plain_text', filterOperators: OPERATOR_TYPES_6, }, + { + key: 'company_name', + name: 'COMPANY_NAME', + inputType: 'plain_text', + filterOperators: OPERATOR_TYPES_2, + }, { key: 'referer', name: 'REFERER_LINK', @@ -314,6 +326,12 @@ export const AUTOMATIONS = { inputType: 'plain_text', filterOperators: OPERATOR_TYPES_6, }, + { + key: 'company_name', + name: 'COMPANY_NAME', + inputType: 'plain_text', + filterOperators: OPERATOR_TYPES_2, + }, { key: 'assignee_id', name: 'ASSIGNEE_NAME', @@ -460,6 +478,12 @@ export const AUTOMATIONS = { inputType: 'plain_text', filterOperators: OPERATOR_TYPES_6, }, + { + key: 'company_name', + name: 'COMPANY_NAME', + inputType: 'plain_text', + filterOperators: OPERATOR_TYPES_2, + }, { key: 'team_id', name: 'TEAM_NAME', @@ -590,6 +614,12 @@ export const AUTOMATIONS = { inputType: 'plain_text', filterOperators: OPERATOR_TYPES_6, }, + { + key: 'company_name', + name: 'COMPANY_NAME', + inputType: 'plain_text', + filterOperators: OPERATOR_TYPES_2, + }, { key: 'team_id', name: 'TEAM_NAME', diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue index caf9e2a6a..b49bae086 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue @@ -31,6 +31,8 @@ const backupCodesDialogRef = ref(null); // Form values const disablePassword = ref(''); const disableOtpCode = ref(''); +const disableBackupCode = ref(''); +const useBackupCodeToDisable = ref(false); const regenerateOtpCode = ref(''); // Utility functions @@ -54,10 +56,17 @@ const downloadBackupCodes = () => { const handleDisableMfa = async () => { emit('disableMfa', { password: disablePassword.value, - otpCode: disableOtpCode.value, + otpCode: useBackupCodeToDisable.value ? '' : disableOtpCode.value, + backupCode: useBackupCodeToDisable.value ? disableBackupCode.value : '', }); }; +const toggleDisableMethod = () => { + useBackupCodeToDisable.value = !useBackupCodeToDisable.value; + disableOtpCode.value = ''; + disableBackupCode.value = ''; +}; + const handleRegenerateBackupCodes = async () => { emit('regenerateBackupCodes', { otpCode: regenerateOtpCode.value, @@ -68,6 +77,8 @@ const handleRegenerateBackupCodes = async () => { const resetDisableForm = () => { disablePassword.value = ''; disableOtpCode.value = ''; + disableBackupCode.value = ''; + useBackupCodeToDisable.value = false; disableDialogRef.value?.close(); }; @@ -157,12 +168,32 @@ defineExpose({ :label="$t('MFA_SETTINGS.DISABLE.PASSWORD')" /> + +