diff --git a/src/App.vue b/src/App.vue index 792692d..611f3b2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -94,6 +94,7 @@ import { getDisplayAppVersion } from '@/config/config-cache' import { computed, defineAsyncComponent, h, onMounted, ref, watch } from 'vue' import { useRoute } from 'vue-router' import { message } from 'ant-design-vue' +import { showBrowserNotification } from '@/utils/browserNotification' import img_topGradiant from '@/views/login/assets/img/gr_top_white.svg' import img_pattern from '@/views/login/assets/img/pattern.svg' import { LoadingOutlined } from '@ant-design/icons-vue' @@ -231,9 +232,20 @@ watch(latestIncomingNotification, (payload) => { if (!payload) return if (payload.at === lastSupportToastAt) return lastSupportToastAt = payload.at - message.info({ - content: `پیام جدید پشتیبانی: ${payload.body}`, - duration: 3, + + // فقط وقتی خارج از صفحهٔ چت (گیت داخل chat.ts)؛ داخل چت نوتیف نمی‌آید + if (typeof document === 'undefined' || document.visibilityState === 'visible') { + message.info({ + content: `پیام جدید پشتیبانی: ${payload.body}`, + duration: 3, + }) + } + + void showBrowserNotification({ + title: payload.title || 'پشتیبانی', + body: payload.body || 'پیام جدید', + tag: 'support-chat-message', + url: '/support', }) }) diff --git a/src/views/support/components/SupportChatComposer.vue b/src/views/support/components/SupportChatComposer.vue index 563acbd..eb54086 100644 --- a/src/views/support/components/SupportChatComposer.vue +++ b/src/views/support/components/SupportChatComposer.vue @@ -173,8 +173,9 @@
@@ -577,14 +578,14 @@ watch( \ No newline at end of file diff --git a/src/views/support/components/SupportChatHeader.vue b/src/views/support/components/SupportChatHeader.vue index 7f737c7..e632787 100644 --- a/src/views/support/components/SupportChatHeader.vue +++ b/src/views/support/components/SupportChatHeader.vue @@ -5,7 +5,14 @@
+

{{ title }}

-
\ No newline at end of file + diff --git a/src/views/support/components/SupportChatMessages.vue b/src/views/support/components/SupportChatMessages.vue index 128ac45..abe604f 100644 --- a/src/views/support/components/SupportChatMessages.vue +++ b/src/views/support/components/SupportChatMessages.vue @@ -11,7 +11,12 @@ class="support-jump-badge-wrap" > @@ -102,7 +107,14 @@ @pointercancel="onMessagePointerCancel" >
+
+ () +const config = useConfigStore() +const supportLogoUrl = computed(() => String(config.config?.avatar || '').trim()) + const scrollContainer = ref(null) const previewImageUrl = ref('') @@ -817,9 +840,10 @@ defineExpose({ .support-jump-wrap { position: absolute; - right: 0; - bottom: 18px; - transform: translateX(-50%); + inset-inline-start: 12px; + right: auto; + bottom: 14px; + transform: none; z-index: 4; } @@ -1030,17 +1054,26 @@ defineExpose({ } } .support-jump-button { - width: 46px; - height: 46px; + width: 44px; + height: 44px; display: inline-flex; align-items: center; justify-content: center; - border: 1px solid rgb(var(--colorPrimaryRGB) / 0.18); - background: rgb(var(--colorPrimaryRGB) / 0.12); - color: var(--colorPrimary, #2563eb); + border: 1.5px solid color-mix(in srgb, var(--colorPrimary, #2563eb) 45%, transparent); + background: color-mix(in srgb, var(--colorPrimary, #2563eb) 42%, transparent); + color: #fff; border-radius: 999px; - box-shadow: 0 10px 15px rgb(var(--colorPrimaryRGB) / 0.12); - backdrop-filter: blur(5px); + box-shadow: + 0 10px 24px color-mix(in srgb, var(--colorPrimary, #2563eb) 28%, transparent), + inset 0 1px 0 rgb(255 255 255 / 0.28); + backdrop-filter: blur(14px) saturate(1.2); + -webkit-backdrop-filter: blur(14px) saturate(1.2); +} + +.support-jump-button :deep(svg), +.support-jump-button :deep(i), +.support-jump-button :deep(.iconify) { + color: #fff !important; } .support-jump-badge-wrap :deep(.ant-badge-count) { @@ -1069,15 +1102,9 @@ defineExpose({ overflow-x: hidden; overscroll-behavior: contain; padding: 16px 12px 16px; - background: radial-gradient( - circle at top right, - rgb(var(--colorPrimaryRGB) / 0.08), - transparent 22% - ), - radial-gradient(circle at bottom left, rgb(var(--colorPrimaryRGB) / 0.05), transparent 24%), - var(--bg, #fafafa); + background: transparent; scrollbar-width: thin; - scrollbar-color: #cfcfd4 #f3f3f5; + scrollbar-color: #cfcfd4 transparent; } .support-chat-scroll::-webkit-scrollbar { @@ -1102,13 +1129,13 @@ defineExpose({ } .support-day-group { - display: block; + display: contents; } .support-day-sticky { position: sticky; top: 0; - z-index: 2; + z-index: 6; display: flex; justify-content: center; pointer-events: none; @@ -1177,17 +1204,28 @@ defineExpose({ min-width: 34px !important; height: 34px !important; padding: 0 !important; - border: 1px solid rgb(var(--colorPrimaryRGB) / 0.18) !important; + border: 1px solid color-mix(in srgb, var(--colorPrimary, #2563eb) 18%, transparent) !important; border-radius: 999px !important; background: color-mix(in srgb, var(--bg, #fff) 92%, transparent) !important; color: var(--colorPrimary, #2563eb) !important; display: inline-flex; align-items: center; justify-content: center; - box-shadow: 0 8px 18px rgb(var(--colorPrimaryRGB) / 0.1); + box-shadow: 0 8px 18px color-mix(in srgb, var(--colorPrimary, #2563eb) 10%, transparent); backdrop-filter: blur(8px); + opacity: 0; + pointer-events: none; + transform: scale(0.92); + transition: + opacity 0.15s ease, + transform 0.15s ease; +} + +.support-row:hover .support-reply-button, +.support-row:focus-within .support-reply-button { opacity: 1; - transform: scale(0.96); + pointer-events: auto; + transform: scale(1); } .support-reply-button--user { @@ -1200,16 +1238,30 @@ defineExpose({ .support-avatar { margin-bottom: 4px; - width: 32px; - height: 32px; - flex: 0 0 32px; - border-radius: 16px; - border: 1px solid rgb(var(--colorPrimaryRGB) / 0.18); - background: rgb(var(--colorPrimaryRGB) / 0.08); + width: 34px; + height: 34px; + flex: 0 0 34px; + overflow: hidden; + border-radius: 999px; + border: 1.5px solid color-mix(in srgb, var(--colorPrimary, #2563eb) 28%, transparent); + background: #fff; color: var(--colorPrimary, #2563eb); display: flex; align-items: center; justify-content: center; + box-shadow: + 0 6px 14px color-mix(in srgb, var(--colorPrimary, #2563eb) 16%, transparent), + inset 0 0 0 1px rgb(255 255 255 / 0.7); +} + +.support-avatar__logo { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; + padding: 4px; + background: #fff; } .support-avatar__svg { @@ -1225,8 +1277,8 @@ defineExpose({ overflow: hidden; border-radius: 24px; padding: 12px 16px; - background: var(--color6, #fff); - box-shadow: 0 8px 18px rgb(var(--colorPrimaryRGB) / 0.08); + background: var(--bg2, #fff); + box-shadow: 0 8px 18px rgb(var(--colorPrimaryRGB, 37 99 235) / 0.08); direction: rtl; text-align: right; } @@ -1235,6 +1287,17 @@ defineExpose({ min-width: min(260px, 72vw); } +.support-bubble--user { + border-bottom-right-radius: 10px; + background: var(--colorPrimary, #2563eb); + color: #fff; + border: none; + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--colorPrimary, #2563eb) 24%, transparent), + 0 12px 32px color-mix(in srgb, var(--colorPrimary, #2563eb) 38%, transparent), + 0 4px 12px color-mix(in srgb, var(--colorPrimary, #2563eb) 22%, transparent); +} + .support-bubble--user .support-bubble__image-wrap { background: rgba(255, 255, 255, 0.12); } @@ -1243,7 +1306,10 @@ defineExpose({ border-bottom-left-radius: 10px; background: var(--bg2, #fff); color: var(--colorText, #111827); - border: 1px solid var(--colorPrimary, #2563eb); + border: 1px solid color-mix(in srgb, var(--colorPrimary, #2563eb) 28%, transparent); + box-shadow: + 0 10px 24px color-mix(in srgb, var(--colorPrimary, #2563eb) 12%, transparent), + 0 2px 6px color-mix(in srgb, var(--colorPrimary, #2563eb) 8%, transparent); } .support-bubble__reply { @@ -1566,12 +1632,16 @@ defineExpose({ } .support-avatar { - width: 28px; - height: 28px; - flex: 0 0 28px; + width: 30px; + height: 30px; + flex: 0 0 30px; margin-bottom: 2px; } + .support-avatar__logo { + padding: 3px; + } + .support-avatar__svg { width: 15px; height: 15px; @@ -1646,15 +1716,20 @@ defineExpose({ display: none; } + .support-reply-button { + opacity: 1; + pointer-events: auto; + transform: scale(1); + } + .support-jump-wrap { - right: 10px; - bottom: 14px; - transform: none; + inset-inline-start: 10px; + bottom: 12px; } .support-jump-button { - width: 44px; - height: 44px; + width: 40px; + height: 40px; } } diff --git a/src/views/support/index.vue b/src/views/support/index.vue index ee00960..564fcc6 100644 --- a/src/views/support/index.vue +++ b/src/views/support/index.vue @@ -51,6 +51,10 @@ import { setSupportChatPageOpen } from '@/utils/chat' import { useSettingsStore } from '@/store/settingsStore' +import { + canRequestBrowserNotification, + requestBrowserNotificationPermission, +} from '@/utils/browserNotification' const draft = ref('') const isSending = ref(false) @@ -509,6 +513,10 @@ const messages = computed(() => { onMounted(async () => { setSupportChatPageOpen(true) + if (canRequestBrowserNotification()) { + void requestBrowserNotificationPermission() + } + try { const existing = getExistingChatSession() @@ -681,7 +689,23 @@ const sendMessage = async (payload: ComposerSendPayload) => { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; - background: var(--bg, #fff); + background: + radial-gradient( + ellipse 110% 70% at 100% -5%, + color-mix(in srgb, var(--colorPrimary, #2563eb) 28%, transparent), + transparent 68% + ), + radial-gradient( + ellipse 95% 60% at -5% 105%, + color-mix(in srgb, var(--colorPrimary, #2563eb) 18%, transparent), + transparent 62% + ), + radial-gradient( + circle at 50% 45%, + color-mix(in srgb, var(--colorPrimary, #2563eb) 8%, transparent), + transparent 55% + ), + var(--bg, #f7f8fb); border: 0; border-radius: 0; box-shadow: none;