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:
2026-08-06 20:14:46 +02:00
co-authored by Cursor
parent be896fe247
commit 42aef88064
55 changed files with 7488 additions and 434 deletions
@@ -4,8 +4,10 @@ import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProd
import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price'
export type ChartMetalType = 'katod' | 'maftol' | 'shamsh'
export type ChartTimeline = 'DAILY' | 'WEEKLY' | 'MONTHLY'
type ChartPoint = [number, number]
type PricesMap = Record<string, Array<{ timestamp: number; value: number }>>
/**
* مارک‌آپ ثابت روی قیمت بورس کالا برای نمودار:
@@ -22,7 +24,8 @@ const priceChartSeries = ref<{ name: string; data: ChartPoint[] }[]>([
{ name: 'قیمت', data: [] },
])
const rawChartPoints = ref<ChartPoint[]>([])
const chartSelection = ref('DAILY')
const cachedPrices = ref<PricesMap | null>(null)
const chartSelection = ref<ChartTimeline>('DAILY')
const chartLoading = ref(false)
const selectedChartMetal = ref<ChartMetalType>('katod')
const catalogProducts = getProductsCatalogRef()
@@ -67,7 +70,7 @@ function normalizeChartTimestamp(timestamp: number) {
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp
}
function resolvePricesMap(response: unknown): Record<string, Array<{ timestamp: number; value: number }>> | null {
function resolvePricesMap(response: unknown): PricesMap | null {
if (!response || typeof response !== 'object') {
return null
}
@@ -76,11 +79,11 @@ function resolvePricesMap(response: unknown): Record<string, Array<{ timestamp:
const nested = body.data
if (nested && typeof nested === 'object' && 'prices' in nested) {
return (nested as { prices: Record<string, Array<{ timestamp: number; value: number }>> }).prices
return (nested as { prices: PricesMap }).prices
}
if ('prices' in body) {
return body.prices as Record<string, Array<{ timestamp: number; value: number }>>
return body.prices as PricesMap
}
return null
@@ -95,9 +98,21 @@ function buildChartPoints(items: Array<{ timestamp: number; value: number }>): C
function syncChartSeries() {
const ratio = selectedMetalChartRatio.value
priceChartSeries.value[0].data = rawChartPoints.value.map(
([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint,
)
// New array so Vue invalidates parent computeds (nested .data mutation did not).
priceChartSeries.value = [
{
name: 'قیمت',
data: rawChartPoints.value.map(
([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint,
),
},
]
}
function applyTimelineFromCache(timeline: ChartTimeline = chartSelection.value) {
const selectedData = cachedPrices.value?.[timeline] ?? []
rawChartPoints.value = buildChartPoints(selectedData)
syncChartSeries()
}
watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => {
@@ -105,8 +120,8 @@ watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => {
})
async function fetchPrices(force = false) {
if (!force && chartInitialized && rawChartPoints.value.length > 0) {
syncChartSeries()
if (!force && chartInitialized && cachedPrices.value) {
applyTimelineFromCache()
return
}
@@ -119,10 +134,9 @@ async function fetchPrices(force = false) {
chartLoading.value = true
const response = await getAllPrices()
const data = resolvePricesMap(response)
const selectedData = data?.[chartSelection.value] ?? []
rawChartPoints.value = buildChartPoints(selectedData)
syncChartSeries()
chartInitialized = true
cachedPrices.value = data
applyTimelineFromCache()
chartInitialized = Boolean(data)
} catch (error) {
console.error('Error fetching prices:', error)
} finally {
@@ -160,7 +174,11 @@ async function loadMetalChartOffsets(force = false) {
}
function updateChartData(timeline: string) {
chartSelection.value = timeline
chartSelection.value = timeline as ChartTimeline
if (cachedPrices.value) {
applyTimelineFromCache(chartSelection.value)
return
}
void fetchPrices(true)
}
@@ -197,6 +215,7 @@ export function loadHomeMaftolOffset(force = false) {
export function resetHomePriceChart() {
priceChartSeries.value = [{ name: 'قیمت', data: [] }]
rawChartPoints.value = []
cachedPrices.value = null
chartSelection.value = 'DAILY'
selectedChartMetal.value = 'katod'
chartInitialized = false