diff --git a/VERSION_CWCTL b/VERSION_CWCTL index 18091983f..47b322c97 100644 --- a/VERSION_CWCTL +++ b/VERSION_CWCTL @@ -1 +1 @@ -3.4.0 +3.4.1 diff --git a/app/controllers/api/v1/accounts/portals_controller.rb b/app/controllers/api/v1/accounts/portals_controller.rb index 6cfed161f..18538e842 100644 --- a/app/controllers/api/v1/accounts/portals_controller.rb +++ b/app/controllers/api/v1/accounts/portals_controller.rb @@ -47,6 +47,20 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController head :ok end + def send_instructions + email = permitted_params[:email] + return render_could_not_create_error(I18n.t('portals.send_instructions.email_required')) if email.blank? + return render_could_not_create_error(I18n.t('portals.send_instructions.invalid_email_format')) unless valid_email?(email) + return render_could_not_create_error(I18n.t('portals.send_instructions.custom_domain_not_configured')) if @portal.custom_domain.blank? + + PortalInstructionsMailer.send_cname_instructions( + portal: @portal, + recipient_email: email + ).deliver_later + + render json: { message: I18n.t('portals.send_instructions.instructions_sent_successfully') }, status: :ok + end + def process_attached_logo blob_id = params[:blob_id] blob = ActiveStorage::Blob.find_by(id: blob_id) @@ -60,12 +74,12 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController end def permitted_params - params.permit(:id) + params.permit(:id, :email) end def portal_params params.require(:portal).permit( - :account_id, :color, :custom_domain, :header_text, :homepage_link, + :id, :account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug, :archived, { config: [:default_locale, { allowed_locales: [] }] } ) end @@ -88,4 +102,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController domain = URI.parse(@portal.custom_domain) domain.is_a?(URI::HTTP) ? domain.host : @portal.custom_domain end + + def valid_email?(email) + ValidEmail2::Address.new(email).valid? + end end + +Api::V1::Accounts::PortalsController.prepend_mod_with('Api::V1::Accounts::PortalsController') diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index 675f6ea67..0fbb20ea9 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -136,7 +136,7 @@ export default {
diff --git a/app/javascript/dashboard/api/captain/scenarios.js b/app/javascript/dashboard/api/captain/scenarios.js new file mode 100644 index 000000000..3e61c28a3 --- /dev/null +++ b/app/javascript/dashboard/api/captain/scenarios.js @@ -0,0 +1,36 @@ +/* global axios */ +import ApiClient from '../ApiClient'; + +class CaptainScenarios extends ApiClient { + constructor() { + super('captain/assistants', { accountScoped: true }); + } + + get({ assistantId, page = 1, searchKey } = {}) { + return axios.get(`${this.url}/${assistantId}/scenarios`, { + params: { page, searchKey }, + }); + } + + show({ assistantId, id }) { + return axios.get(`${this.url}/${assistantId}/scenarios/${id}`); + } + + create({ assistantId, ...data } = {}) { + return axios.post(`${this.url}/${assistantId}/scenarios`, { + scenario: data, + }); + } + + update({ assistantId, id }, data = {}) { + return axios.put(`${this.url}/${assistantId}/scenarios/${id}`, { + scenario: data, + }); + } + + delete({ assistantId, id }) { + return axios.delete(`${this.url}/${assistantId}/scenarios/${id}`); + } +} + +export default new CaptainScenarios(); diff --git a/app/javascript/dashboard/api/captain/tools.js b/app/javascript/dashboard/api/captain/tools.js new file mode 100644 index 000000000..20edaa95e --- /dev/null +++ b/app/javascript/dashboard/api/captain/tools.js @@ -0,0 +1,16 @@ +/* global axios */ +import ApiClient from '../ApiClient'; + +class CaptainTools extends ApiClient { + constructor() { + super('captain/assistants/tools', { accountScoped: true }); + } + + get(params = {}) { + return axios.get(this.url, { + params, + }); + } +} + +export default new CaptainTools(); diff --git a/app/javascript/dashboard/api/helpCenter/portals.js b/app/javascript/dashboard/api/helpCenter/portals.js index 7c6210dbd..d65dcaf1a 100644 --- a/app/javascript/dashboard/api/helpCenter/portals.js +++ b/app/javascript/dashboard/api/helpCenter/portals.js @@ -21,6 +21,14 @@ class PortalsAPI extends ApiClient { deleteLogo(portalSlug) { return axios.delete(`${this.url}/${portalSlug}/logo`); } + + sendCnameInstructions(portalSlug, email) { + return axios.post(`${this.url}/${portalSlug}/send_instructions`, { email }); + } + + sslStatus(portalSlug) { + return axios.get(`${this.url}/${portalSlug}/ssl_status`); + } } export default PortalsAPI; diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss index 66416c64c..40ca7b436 100644 --- a/app/javascript/dashboard/assets/scss/_woot.scss +++ b/app/javascript/dashboard/assets/scss/_woot.scss @@ -37,30 +37,6 @@ body { width: 100%; } -.app-wrapper { - @apply h-screen flex-grow-0 min-h-0 w-full; - - .button--fixed-top { - @apply fixed ltr:right-2 rtl:left-2 top-2 flex flex-row; - } -} - -.banner + .app-wrapper { - // Reduce the height of the dashboard to make room for the banner. - // And causing the top right green-action button to be pushed down when scrolling. - @apply h-[calc(100%-48px)]; - - .button--fixed-top { - @apply top-14; - } - - .off-canvas-content { - .button--fixed-top { - @apply top-2; - } - } -} - .tooltip { @apply bg-n-solid-2 text-n-slate-12 py-1 px-2 z-40 text-xs rounded-md max-w-96; } diff --git a/app/javascript/dashboard/components-next/Editor/Editor.vue b/app/javascript/dashboard/components-next/Editor/Editor.vue index 9e5ff6ab5..a2f139bdc 100644 --- a/app/javascript/dashboard/components-next/Editor/Editor.vue +++ b/app/javascript/dashboard/components-next/Editor/Editor.vue @@ -20,6 +20,7 @@ const props = defineProps({ enableVariables: { type: Boolean, default: false }, enableCannedResponses: { type: Boolean, default: true }, enabledMenuOptions: { type: Array, default: () => [] }, + enableCaptainTools: { type: Boolean, default: false }, }); const emit = defineEmits(['update:modelValue']); @@ -98,6 +99,7 @@ watch( :enable-variables="enableVariables" :enable-canned-responses="enableCannedResponses" :enabled-menu-options="enabledMenuOptions" + :enable-captain-tools="enableCaptainTools" @input="handleInput" @focus="handleFocus" @blur="handleBlur" diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/AddCustomDomainDialog.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/AddCustomDomainDialog.vue index b43e2f2fd..0791f0519 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/AddCustomDomainDialog.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/AddCustomDomainDialog.vue @@ -1,6 +1,9 @@ diff --git a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalSettings.vue b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalSettings.vue index 02b5001b9..83800e002 100644 --- a/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalSettings.vue +++ b/app/javascript/dashboard/components-next/HelpCenter/Pages/PortalSettingsPage/PortalSettings.vue @@ -26,6 +26,8 @@ const emit = defineEmits([ 'updatePortal', 'updatePortalConfiguration', 'deletePortal', + 'refreshStatus', + 'sendCnameInstructions', ]); const { t } = useI18n(); @@ -36,6 +38,7 @@ const confirmDeletePortalDialogRef = ref(null); const currentPortalSlug = computed(() => route.params.portalSlug); const isSwitchingPortal = useMapGetter('portals/isSwitchingPortal'); +const isFetchingSSLStatus = useMapGetter('portals/isFetchingSSLStatus'); const activePortal = computed(() => { return props.portals?.find(portal => portal.slug === currentPortalSlug.value); @@ -53,6 +56,14 @@ const handleUpdatePortalConfiguration = portal => { emit('updatePortalConfiguration', portal); }; +const fetchSSLStatus = () => { + emit('refreshStatus'); +}; + +const handleSendCnameInstructions = payload => { + emit('sendCnameInstructions', payload); +}; + const openConfirmDeletePortalDialog = () => { confirmDeletePortalDialogRef.value.dialogRef.open(); }; @@ -85,7 +96,10 @@ const handleDeletePortal = () => {
diff --git a/app/javascript/dashboard/components-next/captain/assistant/AddNewRulesDialog.vue b/app/javascript/dashboard/components-next/captain/assistant/AddNewRulesDialog.vue index ecdb2d654..c1a465c64 100644 --- a/app/javascript/dashboard/components-next/captain/assistant/AddNewRulesDialog.vue +++ b/app/javascript/dashboard/components-next/captain/assistant/AddNewRulesDialog.vue @@ -1,5 +1,6 @@