fix(ui): repair PWA install, credit footer, and boursecu copy

Make install guide and BasicButton clicks reliable, align montzar credit bar, remove calculator quick action, and use مس in about-software default text.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-03 13:53:10 -04:00
co-authored by Cursor
parent 9356a2ab5e
commit 5920866a95
9 changed files with 85 additions and 28 deletions
+20 -6
View File
@@ -73,17 +73,17 @@
<div <div
v-if="!showSplash && !isDesktop && !isTvBoardRoute" v-if="!showSplash && !isDesktop && !isTvBoardRoute"
class="app-credit-footer app-credit-footer--mobile" class="app-credit-footer app-credit-footer--mobile"
:class="{ 'app-credit-footer--login': isLoginRoute }" :class="{ 'app-credit-footer--login': showLoginCreditExtras }"
> >
<EnamadBadge v-if="isLoginRoute" size="sm" /> <EnamadBadge v-if="showLoginCreditExtras" size="sm" />
<AppCreditLink variant="mobile" /> <AppCreditLink variant="mobile" />
</div> </div>
<div <div
v-if="!showSplash && showDesktopFloatingCredit && !isTvBoardRoute" v-if="!showSplash && showDesktopFloatingCredit && !isTvBoardRoute"
class="app-credit-footer app-credit-footer--desktop-float" class="app-credit-footer app-credit-footer--desktop-float"
:class="{ 'app-credit-footer--login': isLoginRoute }" :class="{ 'app-credit-footer--login': showLoginCreditExtras }"
> >
<EnamadBadge v-if="isLoginRoute" size="sm" /> <EnamadBadge v-if="showLoginCreditExtras" size="sm" />
<AppCreditLink variant="float" /> <AppCreditLink variant="float" />
</div> </div>
@@ -297,6 +297,12 @@ watch(
const userStore = useUserStore() const userStore = useUserStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const showLoginCreditExtras = computed(() => {
if (!isLoginRoute.value) return false
const href = String(settingsStore.getSettingByKey('enamad_url') ?? '').trim()
const src = String(settingsStore.getSettingByKey('enamad_logo') ?? '').trim()
return Boolean(href && src)
})
const { latestIncomingNotification } = useChatService() const { latestIncomingNotification } = useChatService()
let lastConnectedChatSessionKey = '' let lastConnectedChatSessionKey = ''
let lastSupportToastAt = 0 let lastSupportToastAt = 0
@@ -424,13 +430,14 @@ watch(latestIncomingNotification, (payload) => {
min-height: var(--app-credit-bar-inner); min-height: var(--app-credit-bar-inner);
padding: 4px 0 var(--app-safe-bottom); padding: 4px 0 var(--app-safe-bottom);
background: #fff; background: #fff;
box-shadow: 0 -2px 4px -2px #0000001a; box-shadow: 0 3.75px 16px 0 rgba(0, 0, 0, 0.43);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
font-size: 9px; font-size: 9px;
color: #000;
} }
.app-credit-footer--login.app-credit-footer--mobile { .app-credit-footer--login.app-credit-footer--mobile {
@@ -461,6 +468,7 @@ watch(latestIncomingNotification, (payload) => {
&:hover { &:hover {
opacity: 0.85; opacity: 0.85;
color: inherit;
} }
&__text { &__text {
@@ -470,13 +478,19 @@ watch(latestIncomingNotification, (payload) => {
&__logo { &__logo {
width: auto; width: auto;
height: 12px;
object-fit: contain; object-fit: contain;
display: block;
} }
&--mobile { &--mobile {
gap: 4px; gap: 4px;
padding: 4px 0; padding: 4px 0;
color: inherit; color: #000;
.app-credit-link__text {
color: #000;
}
.app-credit-link__logo { .app-credit-link__logo {
height: 12px; height: 12px;
+6 -1
View File
@@ -7,11 +7,16 @@
:class="`app-credit-link--${variant}`" :class="`app-credit-link--${variant}`"
> >
<span class="app-credit-link__text">توسعه و طراحی توسط</span> <span class="app-credit-link__text">توسعه و طراحی توسط</span>
<img src="/logo-montzar.png" alt="montzar" class="app-credit-link__logo" /> <img :src="logoSrc" alt="montzar" class="app-credit-link__logo" />
</a> </a>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const logoSrc = `${import.meta.env.BASE_URL || '/'}logo-montzar.png`.replace(
/([^:]\/)\/+/g,
'$1',
)
withDefaults( withDefaults(
defineProps<{ defineProps<{
variant?: 'mobile' | 'sidebar' | 'float' | 'tv' variant?: 'mobile' | 'sidebar' | 'float' | 'tv'
+20 -3
View File
@@ -1,5 +1,13 @@
<template> <template>
<AButton :disabled="props.disabledB" :type="props.type" :size="props.size" :class="[prefixCls, _class]" :loading="props.loading"> <AButton
:disabled="props.disabledB"
:type="props.type"
:size="props.size"
:class="[prefixCls, _class]"
:loading="props.loading"
:html-type="props.htmlType"
@click="onClick"
>
<!-- py-1 px-2--> <!-- py-1 px-2-->
<div <div
class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2" class="flex items-center gap-1 justify-center flex-row-reverse gap-2 py-1 px-2"
@@ -28,7 +36,7 @@ interface Props {
size?: string size?: string
loading?: boolean loading?: boolean
disabledB?:boolean disabledB?:boolean
htmlType?: 'button' | 'submit' | 'reset'
// prefixIcon?: string // prefixIcon?: string
} }
@@ -51,8 +59,17 @@ const { prefixCls } = usePrefix('base-button')
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
iconSize: baseButtonIconSize, iconSize: baseButtonIconSize,
// iconLeft: false, // iconLeft: false,
iconClass: '' iconClass: '',
htmlType: 'submit',
}) })
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
function onClick(event: MouseEvent) {
emit('click', event)
}
</script> </script>
<style lang="less"> <style lang="less">
+14 -7
View File
@@ -20,11 +20,17 @@ const emit = defineEmits<{
close: [] close: []
}>() }>()
const activePlatform = ref<PwaPlatform>(props.platform ?? getDefaultPwaPlatform()) function normalizePlatform(value?: string | null): PwaPlatform {
return value === 'ios' || value === 'android' ? value : getDefaultPwaPlatform()
}
const activePlatform = ref<PwaPlatform>(normalizePlatform(props.platform))
const stepIndex = ref(0) const stepIndex = ref(0)
const guide = computed(() => PWA_INSTALL_GUIDE[activePlatform.value]) const guide = computed(
const currentStep = computed(() => guide.value.steps[stepIndex.value]) () => PWA_INSTALL_GUIDE[normalizePlatform(activePlatform.value)] ?? PWA_INSTALL_GUIDE.android,
)
const currentStep = computed(() => guide.value.steps[stepIndex.value] ?? guide.value.steps[0])
const totalSteps = computed(() => guide.value.steps.length) const totalSteps = computed(() => guide.value.steps.length)
const isFirstStep = computed(() => stepIndex.value === 0) const isFirstStep = computed(() => stepIndex.value === 0)
const isLastStep = computed(() => stepIndex.value === totalSteps.value - 1) const isLastStep = computed(() => stepIndex.value === totalSteps.value - 1)
@@ -34,7 +40,7 @@ watch(
() => props.open, () => props.open,
(visible) => { (visible) => {
if (!visible) return if (!visible) return
activePlatform.value = props.platform ?? getDefaultPwaPlatform() activePlatform.value = normalizePlatform(props.platform)
stepIndex.value = 0 stepIndex.value = 0
}, },
) )
@@ -42,7 +48,7 @@ watch(
watch( watch(
() => props.platform, () => props.platform,
(value) => { (value) => {
if (value) activePlatform.value = value if (value) activePlatform.value = normalizePlatform(value)
}, },
) )
@@ -52,8 +58,9 @@ function close() {
} }
function selectPlatform(next: PwaPlatform) { function selectPlatform(next: PwaPlatform) {
if (activePlatform.value === next) return const platform = normalizePlatform(next)
activePlatform.value = next if (activePlatform.value === platform) return
activePlatform.value = platform
stepIndex.value = 0 stepIndex.value = 0
} }
+14 -2
View File
@@ -1,4 +1,5 @@
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { message } from 'ant-design-vue'
import type { PwaPlatform } from '@/modules/pwa/pwaInstallGuideSteps' import type { PwaPlatform } from '@/modules/pwa/pwaInstallGuideSteps'
import { import {
getDefaultPwaPlatform, getDefaultPwaPlatform,
@@ -77,7 +78,10 @@ export function usePwaInstall() {
// فقط وقتی واقعاً داخل PWA/TWA هستیم راهنما را نشان نده // فقط وقتی واقعاً داخل PWA/TWA هستیم راهنما را نشان نده
if (isStandalonePwa() || isTWA()) return if (isStandalonePwa() || isTWA()) return
consumeInstallBanner() consumeInstallBanner()
platform.value = nextPlatform ?? getDefaultPwaPlatform() const next = nextPlatform === 'ios' || nextPlatform === 'android'
? nextPlatform
: getDefaultPwaPlatform()
platform.value = next
guideOpen.value = true guideOpen.value = true
} }
@@ -108,16 +112,24 @@ export function usePwaInstall() {
/** نصب مستقیم مرورگر؛ اگر ممکن نبود مدال آموزش. */ /** نصب مستقیم مرورگر؛ اگر ممکن نبود مدال آموزش. */
async function tryInstallOrOpenGuide(nextPlatform?: PwaPlatform) { async function tryInstallOrOpenGuide(nextPlatform?: PwaPlatform) {
ensureListener()
refreshOfferInstall() refreshOfferInstall()
if (isStandalonePwa() || isTWA()) return false
if (isStandalonePwa() || isTWA()) {
message.info('نسخه وب‌اپ از قبل نصب شده است')
offerInstall.value = false
return false
}
const installed = await triggerNativePwaInstall() const installed = await triggerNativePwaInstall()
if (installed) { if (installed) {
offerInstall.value = false offerInstall.value = false
consumeInstallBanner() consumeInstallBanner()
message.success('نصب وب‌اپ آغاز شد')
return true return true
} }
// Chrome روی HTTP/IP یا Safari معمولاً prompt بومی ندارد — راهنما را باز کن
openGuide(nextPlatform) openGuide(nextPlatform)
return false return false
} }
+9
View File
@@ -96,6 +96,11 @@ export function initPwaInstallPromptListener() {
if (installListenerInitialized || typeof window === 'undefined') return if (installListenerInitialized || typeof window === 'undefined') return
installListenerInitialized = true installListenerInitialized = true
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredInstallPrompt = e as BeforeInstallPromptEvent
})
window.addEventListener(PWA_MANIFEST_READY_EVENT, (event) => { window.addEventListener(PWA_MANIFEST_READY_EVENT, (event) => {
const installEvent = (event as CustomEvent<{ installEvent?: Event | null }>).detail?.installEvent const installEvent = (event as CustomEvent<{ installEvent?: Event | null }>).detail?.installEvent
@@ -103,6 +108,10 @@ export function initPwaInstallPromptListener() {
deferredInstallPrompt = installEvent as BeforeInstallPromptEvent deferredInstallPrompt = installEvent as BeforeInstallPromptEvent
} }
}) })
window.addEventListener('appinstalled', () => {
deferredInstallPrompt = null
})
} }
export async function triggerNativePwaInstall() { export async function triggerNativePwaInstall() {
@@ -1,6 +1,6 @@
export function getDefaultAboutSoftwareText(projectName: string) { export function getDefaultAboutSoftwareText(projectName: string) {
const name = projectName.trim() || 'این نرم‌افزار' const name = projectName.trim() || 'این نرم‌افزار'
return `${name} بستری امن و آنلاین برای خرید و فروش طلا فراهم کرده تا کاربران بتوانند سرمایه‌های خود را به‌سادگی مدیریت کنند.\n\nهدف ما ارائه تجربه‌ای سریع، شفاف و کاربرپسند در معاملات طلا است.` return `${name} بستری امن و آنلاین برای خرید و فروش مس فراهم کرده تا کاربران بتوانند سرمایه‌های خود را به‌سادگی مدیریت کنند.\n\nهدف ما ارائه تجربه‌ای سریع، شفاف و کاربرپسند در معاملات مس است.`
} }
function looksLikeHtml(value: string) { function looksLikeHtml(value: string) {
@@ -1,5 +1,4 @@
import { getBoursecuActionIconSrc } from '@/utils/config' import { getBoursecuActionIconSrc } from '@/utils/config'
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
import type { HomeQuickActionItem } from '../types' import type { HomeQuickActionItem } from '../types'
export const boursecuQuickActions: HomeQuickActionItem[] = [ export const boursecuQuickActions: HomeQuickActionItem[] = [
@@ -45,10 +44,4 @@ export const boursecuQuickActions: HomeQuickActionItem[] = [
iconSrc: getBoursecuActionIconSrc('convert'), iconSrc: getBoursecuActionIconSrc('convert'),
route: { name: 'buyProduct' }, route: { name: 'buyProduct' },
}, },
{
id: 'calculator',
label: 'محاسبه‌گر',
icon: 'vuesax-linear:chart-2',
handler: () => openGoldDayCalculator(),
},
] ]
+1 -1
View File
@@ -241,7 +241,7 @@
> >
دانلود اپ اندروید دانلود اپ اندروید
</a> </a>
<BasicButton type="primary" class="w-full py-2" html-type="button" @click.prevent="installWebApp"> <BasicButton type="primary" class="w-full py-2" html-type="button" @click="installWebApp">
<div>نصب نسخه وب اپ</div> <div>نصب نسخه وب اپ</div>
</BasicButton> </BasicButton>
</div> </div>