diff --git a/.gitignore b/.gitignore index 77c4a4740..53deb62a8 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,6 @@ yarn-debug.log* # Vite uses dotenv and suggests to ignore local-only env files. See # https://vitejs.dev/guide/env-and-mode.html#env-files *.local + +# Claude.ai config file +CLAUDE.md diff --git a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb index de0ac4db9..fda19b8c2 100644 --- a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb @@ -1,17 +1,21 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::Contacts::BaseController def index - @conversations = Current.account.conversations.includes( + # Start with all conversations for this contact + conversations = Current.account.conversations.includes( :assignee, :contact, :inbox, :taggings - ).where(inbox_id: inbox_ids, contact_id: @contact.id).order(last_activity_at: :desc).limit(20) - end + ).where(contact_id: @contact.id) - private + # Apply permission-based filtering using the existing service + conversations = Conversations::PermissionFilterService.new( + conversations, + Current.user, + Current.account + ).perform - def inbox_ids - if Current.user.administrator? || Current.user.agent? - Current.user.assigned_inboxes.pluck(:id) - else - [] - end + # Only allow conversations from inboxes the user has access to + inbox_ids = Current.user.assigned_inboxes.pluck(:id) + conversations = conversations.where(inbox_id: inbox_ids) + + @conversations = conversations.order(last_activity_at: :desc).limit(20) end end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 138c2bd68..8753918fc 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -48,7 +48,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def filter - result = ::Conversations::FilterService.new(params.permit!, current_user).perform + result = ::Conversations::FilterService.new(params.permit!, current_user, current_account).perform @conversations = result[:conversations] @conversations_count = result[:count] rescue CustomExceptions::CustomFilter::InvalidAttribute, diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index 31f98e384..1694199ba 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -93,6 +93,12 @@ class ConversationFinder def find_all_conversations find_conversation_by_inbox + # Apply permission-based filtering + @conversations = Conversations::PermissionFilterService.new( + @conversations, + current_user, + current_account + ).perform filter_by_conversation_type if params[:conversation_type] @conversations end diff --git a/app/javascript/dashboard/assets/scss/_rtl.scss b/app/javascript/dashboard/assets/scss/_rtl.scss index 36679fc60..4f5f4b843 100644 --- a/app/javascript/dashboard/assets/scss/_rtl.scss +++ b/app/javascript/dashboard/assets/scss/_rtl.scss @@ -81,28 +81,6 @@ margin-left: var(--space-small); } } - - // Conversation sidebar close button - .close-button--rtl { - transform: rotate(180deg); - } - - // Resolve actions button - .resolve-actions { - .button-group .button:first-child { - border-bottom-left-radius: 0; - border-bottom-right-radius: var(--border-radius-normal); - border-top-left-radius: 0; - border-top-right-radius: var(--border-radius-normal); - } - - .button-group .button:last-child { - border-bottom-left-radius: var(--border-radius-normal); - border-bottom-right-radius: 0; - border-top-left-radius: var(--border-radius-normal); - border-top-right-radius: 0; - } - } } // Conversation list @@ -177,71 +155,6 @@ } } - // Help center - .article-container .row--article-block { - td:last-child { - direction: initial; - } - } - - .portal-popover__container .portal { - .actions-container { - margin-left: unset; - margin-right: var(--space-one); - } - } - - .edit-article--container { - .header-right--wrap { - .button-group .button:first-child { - border-bottom-left-radius: 0; - border-bottom-right-radius: var(--border-radius-normal); - border-top-left-radius: 0; - border-top-right-radius: var(--border-radius-normal); - } - - .button-group .button:last-child { - border-bottom-left-radius: var(--border-radius-normal); - border-bottom-right-radius: 0; - border-top-left-radius: var(--border-radius-normal); - border-top-right-radius: 0; - } - } - - .header-left--wrap { - .back-button { - direction: initial; - } - } - - .article--buttons { - .dropdown-pane { - left: 0; - position: absolute; - right: unset; - } - } - - .sidebar-button { - transform: rotate(180deg); - } - } - - .article-settings--container { - border-left: 0; - border-right: 1px solid var(--color-border-light); - flex-direction: row-reverse; - margin-left: 0; - margin-right: var(--space-normal); - padding-left: 0; - padding-right: var(--space-normal); - } - - .category-list--container .header-left--wrap { - direction: initial; - justify-content: flex-end; - } - // Toggle switch .toggle-button { &.small { @@ -264,11 +177,6 @@ } } - // Widget builder - .widget-builder-container .widget-preview { - direction: initial; - } - // Modal .modal-container { text-align: right; @@ -282,7 +190,6 @@ } // Other changes - .colorpicker--chrome { direction: initial; } @@ -291,14 +198,6 @@ direction: initial; } - .contact--details .contact--bio { - direction: ltr; - } - - .merge-contacts .child-contact-wrap { - direction: ltr; - } - .contact--form .input-group { direction: initial; } diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss index 004d8bf59..622eb4085 100644 --- a/app/javascript/dashboard/assets/scss/_woot.scss +++ b/app/javascript/dashboard/assets/scss/_woot.scss @@ -29,7 +29,6 @@ @import 'rtl'; @import 'widgets/base'; -@import 'widgets/buttons'; @import 'widgets/conversation-view'; @import 'widgets/tabs'; @import 'widgets/woot-tables'; diff --git a/app/javascript/dashboard/assets/scss/widgets/_base.scss b/app/javascript/dashboard/assets/scss/widgets/_base.scss index b2c271173..9c4ccf126 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_base.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_base.scss @@ -40,6 +40,12 @@ dl:not(.reset-base) { @apply mb-0; } +// Button base +button { + font-family: inherit; + @apply inline-block text-center align-middle cursor-pointer text-sm m-0 py-1 px-2.5 transition-all duration-200 ease-in-out border-0 border-none rounded-lg disabled:opacity-50; +} + // Form elements // ------------------------- label { diff --git a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss b/app/javascript/dashboard/assets/scss/widgets/_buttons.scss deleted file mode 100644 index 133e07bd1..000000000 --- a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss +++ /dev/null @@ -1,228 +0,0 @@ -// scss-lint:disable SpaceAfterPropertyColon -// scss-lint:disable MergeableSelector -button { - font-family: inherit; - transition: - background-color 0.25s ease-out, - color 0.25s ease-out; - @apply inline-block items-center mb-0 text-center align-middle cursor-pointer text-sm mt-0 mx-0 py-1 px-2.5 border border-solid border-transparent dark:border-transparent rounded-[0.3125rem]; - - &:disabled, - &.disabled { - @apply opacity-40 cursor-not-allowed; - } -} - -.button-group { - @apply mb-0 flex flex-nowrap items-stretch; - - .button { - flex: 0 0 auto; - @apply m-0 text-sm rounded-none first:rounded-tl-[0.3125rem] first:rounded-bl-[0.3125rem] last:rounded-tr-[0.3125rem] last:rounded-br-[0.3125rem] rtl:space-x-reverse; - } - - .button--only-icon { - @apply w-10 justify-center pl-0 pr-0; - } -} - -.back-button { - @apply m-0; -} - -.button { - @apply items-center bg-n-brand px-2.5 text-white dark:text-white inline-flex h-10 mb-0 gap-2 font-medium; - - .button__content { - @apply w-full whitespace-nowrap overflow-hidden text-ellipsis; - - img, - svg { - @apply inline-block; - } - } - - &:hover:not(:disabled):not(.success):not(.alert):not(.warning):not( - .clear - ):not(.smooth):not(.hollow) { - @apply bg-n-brand/80 dark:bg-n-brand/80; - } - - &:disabled, - &.disabled { - @apply opacity-40 cursor-not-allowed; - } - - &.success { - @apply bg-n-teal-9 text-white dark:text-white; - } - - &.secondary { - @apply bg-n-solid-3 text-white dark:text-white; - } - - &.primary { - @apply bg-n-brand text-white dark:text-white; - } - - &.clear { - @apply text-n-blue-text dark:text-n-blue-text bg-transparent dark:bg-transparent; - } - - &.alert { - @apply bg-n-ruby-9 text-white dark:text-white; - - &.clear { - @apply bg-transparent dark:bg-transparent; - } - } - - &.warning { - @apply bg-n-amber-9 text-white dark:text-white; - - &.clear { - @apply bg-transparent dark:bg-transparent; - } - } - - &.tiny { - @apply h-6 text-[10px]; - } - - &.small { - @apply h-8 text-xs; - } - - .spinner { - @apply px-2 py-0; - } - - // @TODDO - Remove after moving all buttons to woot-button - .icon + .button__content { - @apply w-auto; - } - - &.expanded { - @apply flex justify-center text-center; - } - - &.round { - @apply rounded-full; - } - - // @TODO Use with link - - &.compact { - @apply pb-0 pt-0; - } - - &.hollow { - @apply border border-n-brand/40 bg-transparent text-n-blue-text hover:enabled:bg-n-brand/20; - - &.secondary { - @apply text-n-slate-12 border-n-slate-5 hover:enabled:bg-n-slate-5; - } - - &.success { - @apply text-n-teal-9 border-n-teal-8 hover:enabled:bg-n-teal-5; - } - - &.alert { - @apply text-n-ruby-9 border-n-ruby-8 hover:enabled:bg-n-ruby-5; - } - - &.warning { - @apply text-n-amber-9 border-n-amber-8 hover:enabled:bg-n-amber-5; - } - } - - // Smooth style - &.smooth { - @apply bg-n-brand/10 dark:bg-n-brand/30 text-n-blue-text hover:enabled:bg-n-brand/20 dark:hover:enabled:bg-n-brand/40; - - &.secondary { - @apply bg-n-slate-4 text-n-slate-11 hover:enabled:text-n-slate-11 hover:enabled:bg-n-slate-5; - } - - &.success { - @apply bg-n-teal-4 text-n-teal-11 hover:enabled:text-n-teal-11 hover:enabled:bg-n-teal-5; - } - - &.alert { - @apply bg-n-ruby-4 text-n-ruby-11 hover:enabled:text-n-ruby-11 hover:enabled:bg-n-ruby-5; - } - - &.warning { - @apply bg-n-amber-4 text-n-amber-11 hover:enabled:text-n-amber-11 hover:enabled:bg-n-amber-5; - } - } - - &.clear { - @apply text-n-blue-text hover:enabled:bg-n-brand/10 dark:hover:enabled:bg-n-brand/30; - - &.secondary { - @apply text-n-slate-12 hover:enabled:bg-n-slate-4; - } - - &.success { - @apply text-n-teal-10 hover:enabled:bg-n-teal-4; - } - - &.alert { - @apply text-n-ruby-11 hover:enabled:bg-n-ruby-4; - } - - &.warning { - @apply text-n-amber-11 hover:enabled:bg-n-amber-4; - } - - &:active { - &.secondary { - @apply active:bg-n-slate-3 dark:active:bg-n-slate-7; - } - } - - &:focus { - &.secondary { - @apply focus:bg-n-slate-4 dark:focus:bg-n-slate-6; - } - } - } - - // Sizes - &.tiny { - @apply h-6; - } - - &.small { - @apply h-8 pb-1 pt-1; - } - - &.large { - @apply h-12; - } - - &.button--only-icon { - @apply justify-center pl-0 pr-0 w-10; - - &.tiny { - @apply w-6; - } - - &.small { - @apply w-8; - } - - &.large { - @apply w-12; - } - } - - &.link { - @apply h-auto m-0 p-0; - - &:hover { - @apply underline; - } - } -} diff --git a/app/javascript/dashboard/components-next/banner/Banner.vue b/app/javascript/dashboard/components-next/banner/Banner.vue index ed038d4bb..466c03be0 100644 --- a/app/javascript/dashboard/components-next/banner/Banner.vue +++ b/app/javascript/dashboard/components-next/banner/Banner.vue @@ -1,3 +1,5 @@ + + - - - - diff --git a/app/javascript/dashboard/components/SidemenuIcon.vue b/app/javascript/dashboard/components/SidemenuIcon.vue index 59ef4c4c2..50bee3f17 100644 --- a/app/javascript/dashboard/components/SidemenuIcon.vue +++ b/app/javascript/dashboard/components/SidemenuIcon.vue @@ -3,12 +3,16 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { BUS_EVENTS } from 'shared/constants/busEvents'; import { mapGetters } from 'vuex'; import { emitter } from 'shared/helpers/mitt'; +import NextButton from 'dashboard/components-next/button/Button.vue'; export default { + components: { + NextButton, + }, props: { size: { type: String, - default: 'small', + default: 'sm', }, }, computed: { @@ -33,13 +37,13 @@ export default { diff --git a/app/javascript/dashboard/components/app/PendingEmailVerificationBanner.vue b/app/javascript/dashboard/components/app/PendingEmailVerificationBanner.vue index a349786e6..e09f8368a 100644 --- a/app/javascript/dashboard/components/app/PendingEmailVerificationBanner.vue +++ b/app/javascript/dashboard/components/app/PendingEmailVerificationBanner.vue @@ -35,7 +35,7 @@ export default { color-scheme="alert" :banner-message="bannerMessage" :action-button-label="actionButtonMessage" - action-button-icon="mail" + action-button-icon="i-lucide-mail" has-action-button @primary-action="resendVerificationEmail" /> diff --git a/app/javascript/dashboard/components/buttons/Button.vue b/app/javascript/dashboard/components/buttons/Button.vue deleted file mode 100644 index 64b5ecb9a..000000000 --- a/app/javascript/dashboard/components/buttons/Button.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - diff --git a/app/javascript/dashboard/components/buttons/FormSubmitButton.vue b/app/javascript/dashboard/components/buttons/FormSubmitButton.vue deleted file mode 100644 index 283387e7c..000000000 --- a/app/javascript/dashboard/components/buttons/FormSubmitButton.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - - - diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 7c7a9c262..4c537aaac 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -134,7 +134,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);