Port modular features into boursecu and fix desktop login UX.
Add support chat, piggy funds, PWA banner, notification/SSE sync, and messenger legacy guards while keeping boursecu theme; also balance login panels and unblock login navigation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,6 +33,20 @@
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="chatEnabled"
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--notify"
|
||||
aria-label="چت پشتیبانی"
|
||||
@click.stop="goSupportChat"
|
||||
>
|
||||
<span class="mhome-header-btn__icon-wrap">
|
||||
<Icon icon="vuesax-linear:messages-2" size="18" />
|
||||
<span v-if="chatUnreadCount > 0" class="mhome-header-btn__badge">
|
||||
{{ chatUnreadCount > 99 ? '99+' : chatUnreadCount }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mhome-header-btn"
|
||||
@@ -67,6 +81,7 @@ import {
|
||||
hasNativeInstallPrompt,
|
||||
initPwaInstallPromptListener,
|
||||
} from '@/modules/pwa/pwaInstall'
|
||||
import { unreadSupportCount, isUserSupportChatEnabled } from '@/utils/chat'
|
||||
|
||||
const headerRef = ref<HTMLElement | null>(null)
|
||||
const isHeaderScrolled = ref(false)
|
||||
@@ -74,6 +89,8 @@ const canNativeInstall = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
const projectLogo = computed(() => getProjectMobileNavLogo())
|
||||
const chatEnabled = computed(() => isUserSupportChatEnabled())
|
||||
const chatUnreadCount = computed(() => unreadSupportCount.value)
|
||||
const {
|
||||
enabled: notificationEnabled,
|
||||
unreadCount,
|
||||
@@ -98,6 +115,10 @@ async function goNotifications() {
|
||||
void router.push({ name: 'appNotifications' })
|
||||
}
|
||||
|
||||
function goSupportChat() {
|
||||
void router.push({ name: 'support' })
|
||||
}
|
||||
|
||||
let intersectionObserver: IntersectionObserver | null = null
|
||||
let scrollTarget: HTMLElement | Window | null = null
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
<a-radio-button value="WEEKLY" @click="handleUpdate('WEEKLY')">هفته گذشته</a-radio-button>
|
||||
<a-radio-button value="MONTHLY" @click="handleUpdate('MONTHLY')">ماه گذشته</a-radio-button>
|
||||
</a-radio-group>
|
||||
<div ref="chartContainerRef" id="chart-timeline" class="w-full" :class="chartInnerClass">
|
||||
<div id="chart-timeline" class="w-full" :class="chartInnerClass">
|
||||
<apexchart
|
||||
v-if="canRenderChart"
|
||||
:key="`${layout}-${variant}-${selectedMetal}-${chartSelection}-${seriesLength}`"
|
||||
:key="chartRenderKey"
|
||||
type="area"
|
||||
width="100%"
|
||||
:height="chartHeight"
|
||||
@@ -66,7 +66,6 @@ import {
|
||||
shouldUseKatodVisualChartTransform,
|
||||
} from '@/utils/chartPriceAxis'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { price } from '@/utils/price'
|
||||
|
||||
import type { ChartMetalType } from '../composables/usePriceChart'
|
||||
@@ -117,12 +116,10 @@ const emit = defineEmits<{
|
||||
const config = useConfigStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
const { selectedMetalChartRatio } = usePriceChart()
|
||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||
const chartMounted = ref(false)
|
||||
const renderNonce = ref(0)
|
||||
const selectedValue = ref(props.chartSelection)
|
||||
|
||||
const { width: chartWidth } = useElementSize(chartContainerRef)
|
||||
|
||||
const seriesLength = computed(() => props.priceChartSeries?.[0]?.data?.length ?? 0)
|
||||
const amplifyKatodChart = computed(() =>
|
||||
shouldAmplifyKatodMesChart({
|
||||
@@ -175,20 +172,46 @@ const chartYBounds = computed(() => {
|
||||
|
||||
return computeKatodMesVisualYBounds(prices)
|
||||
})
|
||||
const canRenderChart = computed(
|
||||
() => chartMounted.value && chartWidth.value > 0 && seriesLength.value > 0
|
||||
/** Do not gate on measured width — useElementSize can stay 0 until a reflow (e.g. tab click). */
|
||||
const canRenderChart = computed(() => chartMounted.value && seriesLength.value > 0)
|
||||
const chartRenderKey = computed(
|
||||
() =>
|
||||
`${props.layout}-${props.variant}-${props.selectedMetal}-${props.chartSelection}-${seriesLength.value}-${renderNonce.value}`
|
||||
)
|
||||
|
||||
function scheduleChartRemount() {
|
||||
void nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
renderNonce.value += 1
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.chartSelection,
|
||||
(timeline) => {
|
||||
selectedValue.value = timeline
|
||||
if (seriesLength.value > 0) {
|
||||
scheduleChartRemount()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Data often arrives before/while first layout; Apex can mount blank until remount.
|
||||
watch(seriesLength, (len, prev) => {
|
||||
if (len > 0 && (prev ?? 0) === 0) {
|
||||
scheduleChartRemount()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
chartMounted.value = true
|
||||
if (seriesLength.value > 0) {
|
||||
scheduleChartRemount()
|
||||
}
|
||||
})
|
||||
|
||||
const chartOptions = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user