Improve boursecu UX: banners, support FAB, wallet deposit guide, fund/physical layouts, nav, and sidebar credit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-23 14:10:40 +02:00
co-authored by Cursor
parent 21345318ba
commit 4fd12277e7
40 changed files with 1844 additions and 305 deletions
+266
View File
@@ -0,0 +1,266 @@
import { computed, ref } from 'vue'
import { message } from 'ant-design-vue'
import { useConfigStore } from '@/store/configStore'
import {
getProjectBaleContactLink,
getProjectSupportContactLink,
getProjectSupportPhoneLink,
isBazretalaProject,
} from '@/utils/config'
import {
buildDepositSmsUrl,
buildSupportPhoneUrl,
loadSupportMessengerFromSettings,
messengerNeedsClipboardCopy,
openSupportMessengerEntry,
openSupportMessengerWithMessage,
resolveDepositGuideText,
resolveDepositSupportMessage,
resolveSupportMessengerEntries,
resolveSupportMessengerLink,
resolveSupportMessengerMeta,
supportMessengerPickerAnchor,
supportMessengerPickerOpen,
supportMessengerSettings,
type ResolvedSupportMessengerEntry,
} from '@/utils/supportMessenger'
import {
copyDepositFieldValue,
displayDepositCardNumber,
displayDepositSheba,
getDepositGuideVarsFromSettings,
getDepositSettingLabel,
hasDepositFieldValue,
} from '@/utils/depositGuideText'
import { useSettingsStore } from '@/store/settingsStore'
/** plain: منوی شناور / درباره ما — بدون پیش‌متن | deposit: مودال واریز — با deposit_support_message_text */
type SupportPickerIntent = 'plain' | 'deposit'
export function useSupportMessenger() {
const config = useConfigStore()
const settingsStore = useSettingsStore()
const supportPickerOpen = supportMessengerPickerOpen
const supportPickerIntent = ref<SupportPickerIntent>('plain')
const legacySupportLink = computed(() => {
if (config.config?.name === 'garoosi' || isBazretalaProject()) {
return getProjectBaleContactLink()
}
return getProjectSupportContactLink()
})
const depositSupportMessage = computed(() => resolveDepositSupportMessage())
/** لینک‌های تمیز بدون پیش‌متن — متن فقط موقع باز کردن (قصد deposit) اضافه می‌شود */
const supportMessengerEntries = computed(() =>
resolveSupportMessengerEntries(supportMessengerSettings.value),
)
const primaryMessengerEntry = computed(() => supportMessengerEntries.value[0] ?? null)
const messengerType = computed(
() => (primaryMessengerEntry.value?.messenger ?? supportMessengerSettings.value.messenger ?? '').trim().toLowerCase(),
)
const messengerMeta = computed(() =>
primaryMessengerEntry.value
? {
label: primaryMessengerEntry.value.label,
icon: primaryMessengerEntry.value.icon,
accent: primaryMessengerEntry.value.accent,
}
: resolveSupportMessengerMeta(messengerType.value),
)
const messengerLink = computed(() =>
primaryMessengerEntry.value?.url
|| resolveSupportMessengerLink(
supportMessengerSettings.value.messenger,
supportMessengerSettings.value.messengerId,
legacySupportLink.value,
),
)
async function openMessengerEntry(entry: ResolvedSupportMessengerEntry, text?: string) {
// فقط وقتی از مودال واریز آمده‌ایم پیش‌نویس می‌گذاریم؛ منوی شناور همیشه خالی
const messageText =
text !== undefined
? text.trim()
: supportPickerIntent.value === 'deposit'
? depositSupportMessage.value.trim()
: ''
const opened = await openSupportMessengerEntry(entry, messageText, legacySupportLink.value)
if (!opened) return false
if (messageText && messengerNeedsClipboardCopy(entry.messenger)) {
message.success('متن پیام کپی شد — در پیام‌رسان paste کنید')
}
return true
}
let supportPickerOpenedAt = 0
async function openSupportPicker(
intent: SupportPickerIntent,
anchorEl?: HTMLElement | null,
) {
supportPickerIntent.value = intent
if (anchorEl !== undefined) {
supportMessengerPickerAnchor.value = anchorEl
}
const draft =
intent === 'deposit' ? depositSupportMessage.value.trim() : ''
const entries = supportMessengerEntries.value
if (entries.length === 0) {
return openSupportMessengerWithMessage(
supportMessengerSettings.value.messenger,
supportMessengerSettings.value.messengerId,
draft,
legacySupportLink.value,
)
}
if (entries.length === 1) {
return openMessengerEntry(entries[0], draft)
}
const now = Date.now()
if (supportPickerOpen.value) {
if (now - supportPickerOpenedAt < 450) return true
supportPickerOpen.value = false
return true
}
supportPickerOpen.value = true
supportPickerOpenedAt = now
return true
}
/** منوی شناور / صفحات درباره ما — بدون پیش‌نویس پیام */
async function openSupportMessenger(anchorEl?: HTMLElement | null) {
return openSupportPicker('plain', anchorEl)
}
/** مودال راهنمای واریز — با متن deposit_support_message_text */
async function openDepositSupportMessenger(anchorEl?: HTMLElement | null) {
return openSupportPicker('deposit', anchorEl)
}
function handleSupportMessengerClick(event?: Event) {
const target = event?.currentTarget
const anchor = target instanceof HTMLElement ? target : null
void openSupportMessenger(anchor)
}
const depositSmsLink = computed(() =>
buildDepositSmsUrl(
supportMessengerSettings.value.depositSmsSupportNumber,
depositSupportMessage.value,
),
)
const supportPhoneLink = computed(() => {
const fromSettings = buildSupportPhoneUrl(
supportMessengerSettings.value.depositSmsSupportNumber,
)
if (fromSettings) return fromSettings
const legacyPhone = getProjectSupportPhoneLink()
if (legacyPhone && legacyPhone !== 'tel:') return legacyPhone
const legacy = legacySupportLink.value
if (legacy.startsWith('tel:') && legacy.length > 4) return legacy
return ''
})
const supportMessengerText = computed(() => resolveDepositGuideText())
const depositCardNumberRaw = computed(() => {
void settingsStore.settings
const fromMessenger = supportMessengerSettings.value.depositCardNumber?.trim()
if (fromMessenger) return fromMessenger
return String(getDepositGuideVarsFromSettings().cardNumber ?? '').trim()
})
const depositShebaRaw = computed(() => {
void settingsStore.settings
const fromMessenger = supportMessengerSettings.value.depositSheba?.trim()
if (fromMessenger) return fromMessenger
return String(getDepositGuideVarsFromSettings().sheba ?? '').trim()
})
const depositCardNumber = computed(() => displayDepositCardNumber(depositCardNumberRaw.value))
const depositSheba = computed(() => displayDepositSheba(depositShebaRaw.value))
const depositCardLabel = computed(() => {
void settingsStore.settings
return getDepositSettingLabel('deposit_card_number', 'شماره کارت واریز')
})
const depositShebaLabel = computed(() => {
void settingsStore.settings
return getDepositSettingLabel('deposit_sheba', 'شبا واریز')
})
const hasDepositCardValue = computed(() => hasDepositFieldValue(depositCardNumberRaw.value))
const hasDepositShebaValue = computed(() => hasDepositFieldValue(depositShebaRaw.value))
async function copyDepositCardNumber() {
await copyDepositFieldValue(
depositCardNumberRaw.value,
'card',
'شماره کارت ثبت نشده است',
)
}
async function copyDepositShebaNumber() {
await copyDepositFieldValue(
depositShebaRaw.value,
'sheba',
'شماره شبا ثبت نشده است',
)
}
const hasMessengerLink = computed(() => supportMessengerEntries.value.length > 0 || !!messengerLink.value)
const hasMultipleMessengers = computed(() => supportMessengerEntries.value.length > 1)
const hasSupportPhoneLink = computed(() => !!supportPhoneLink.value)
const hasSupportActions = computed(() => hasSupportPhoneLink.value || hasMessengerLink.value)
const opensInNewTab = computed(() => /^https?:\/\//i.test(messengerLink.value))
return {
supportPickerOpen,
supportPickerAnchor: supportMessengerPickerAnchor,
supportMessengerEntries,
messengerType,
messengerMeta,
messengerLink,
supportPhoneLink,
depositSmsLink,
supportMessengerText,
depositSupportMessage,
depositCardNumber,
depositSheba,
depositCardNumberRaw,
depositShebaRaw,
depositCardLabel,
depositShebaLabel,
hasDepositCardValue,
hasDepositShebaValue,
copyDepositCardNumber,
copyDepositShebaNumber,
openSupportMessenger,
openDepositSupportMessenger,
openMessengerEntry,
handleSupportMessengerClick,
hasMessengerLink,
hasMultipleMessengers,
hasSupportPhoneLink,
hasSupportActions,
opensInNewTab,
loadSupportMessengerFromSettings,
}
}