Show negative SMS wallet warning modal on admin page loads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 14:13:51 +03:30
co-authored by Cursor
parent 3ea6b02b47
commit 7e043b24be
5 changed files with 165 additions and 2 deletions
+9 -1
View File
@@ -212,11 +212,19 @@ watch(modalVisible, (open) => {
if (open) showLabelHint.value = false
})
function onOpenChargeFromWarning() {
modalVisible.value = true
}
onMounted(() => {
if (isCompactDesktop.value) startLabelHintLoop()
window.addEventListener('sms-open-charge-modal', onOpenChargeFromWarning)
})
onUnmounted(stopLabelHintTimers)
onUnmounted(() => {
stopLabelHintTimers()
window.removeEventListener('sms-open-charge-modal', onOpenChargeFromWarning)
})
</script>
<style scoped lang="less">
+27
View File
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { computed } from 'vue'
import SmsNegativeBalanceModal from '@/components/SmsNegativeBalanceModal.vue'
import { useSmsWalletStore } from '@/store/smsWalletStore'
const store = useSmsWalletStore()
const visible = computed({
get: () => store.visible && store.loaded && store.negativeWarningVisible,
set: (value: boolean) => {
if (!value) store.dismissNegativeWarning()
}
})
function openCharge() {
// باز کردن مودال شارژ از ویجت هدر با رویداد سفارشی ساده
window.dispatchEvent(new CustomEvent('sms-open-charge-modal'))
}
</script>
<template>
<SmsNegativeBalanceModal
v-model:visible="visible"
:balance="store.balance"
@charge="openCharge"
/>
</template>
+115
View File
@@ -0,0 +1,115 @@
<script setup lang="ts">
import BasicModal from '@/components/BasicModal.vue'
import BaseButton from '@core/components/BaseButton'
import Icon from '@core/components/BaseIcon/src/Icon.vue'
defineProps<{
visible: boolean
balance: number
}>()
const emit = defineEmits<{
'update:visible': [value: boolean]
charge: []
}>()
function closeModal() {
emit('update:visible', false)
}
function openCharge() {
emit('charge')
emit('update:visible', false)
}
function formatNumber(value: number) {
return Number(value || 0).toLocaleString('fa-IR')
}
</script>
<template>
<BasicModal
:visible="visible"
width="28rem"
title="هشدار موجودی پیامک"
:custom-footer="true"
@close="closeModal"
@update:visible="emit('update:visible', $event)"
>
<div class="sms-neg-modal">
<div class="sms-neg-modal__banner">
<Icon icon="vuesax-linear:warning-2" :size="22" class="sms-neg-modal__icon" />
<div>
<p class="sms-neg-modal__title">موجودی کیف پول پیامک منفی است</p>
<p class="sms-neg-modal__text">
تا زمان شارژ و مثبت شدن موجودی، این هشدار با هر بار ورود به صفحات ادمین نمایش داده می‌شود.
</p>
</div>
</div>
<div class="sms-neg-modal__balance">
<span>موجودی فعلی</span>
<strong>{{ formatNumber(balance) }} ریال</strong>
</div>
</div>
<template #footer>
<BaseButton @click="closeModal">بستن</BaseButton>
<BaseButton type="primary" @click="openCharge">اطلاعات واریز / شارژ</BaseButton>
</template>
</BasicModal>
</template>
<style scoped lang="less">
.sms-neg-modal {
display: flex;
flex-direction: column;
gap: 1rem;
}
.sms-neg-modal__banner {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.85rem 1rem;
border-radius: 0.85rem;
border: 1px solid #fecaca;
background: linear-gradient(135deg, #fef2f2, #fff);
}
.sms-neg-modal__icon {
color: #dc2626;
flex-shrink: 0;
margin-top: 0.15rem;
}
.sms-neg-modal__title {
margin: 0 0 0.35rem;
font-weight: 700;
color: #b91c1c;
}
.sms-neg-modal__text {
margin: 0;
font-size: 0.85rem;
line-height: 1.8;
color: var(--admin-text-secondary);
}
.sms-neg-modal__balance {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
background: var(--admin-bg-muted);
border: 1px solid var(--admin-border);
font-size: 0.85rem;
}
.sms-neg-modal__balance strong {
color: #b91c1c;
font-size: 1rem;
}
</style>
+2
View File
@@ -90,6 +90,7 @@
</ALayout>
<BasicMobileBottomNav v-if="isMobile" />
<BaseSettings />
<SmsNegativeBalanceHost />
</ALayout>
</template>
@@ -102,6 +103,7 @@ import { isMobile } from '@core/utils/is'
import BaseSettings from '@core/layouts/BaseSettings.vue'
import BasicMobileHeader from '@/components/BasicMobileHeader.vue'
import BasicMobileBottomNav from '@/components/BasicMobileBottomNav.vue'
import SmsNegativeBalanceHost from '@/components/SmsNegativeBalanceHost.vue'
const { prefixCls } = usePrefix('basic-layout')
const layoutStore = useLayoutStore()
</script>
+12 -1
View File
@@ -1,4 +1,5 @@
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { getSmsWalletSummary, type SmsDepositInfo } from '@/api/smsPanel'
import { useUserStore } from '@/store/userStore'
@@ -18,6 +19,8 @@ export const useSmsWalletStore = defineStore('smsWallet', () => {
const deposit = ref<SmsDepositInfo>({ ...EMPTY_DEPOSIT })
const loaded = ref(false)
const loading = ref(false)
/** هشدار موجودی منفی — با هر refresh اگر هنوز منفی باشد دوباره باز می‌شود */
const negativeWarningVisible = ref(false)
/** ویجت فقط وقتی billing/پنل پیامک فعال است نمایش داده می‌شود. */
const visible = computed(() => userStore.smsBillingEnabled)
@@ -25,6 +28,7 @@ export const useSmsWalletStore = defineStore('smsWallet', () => {
async function refresh() {
if (!visible.value) {
loaded.value = false
negativeWarningVisible.value = false
return
}
if (loading.value) return
@@ -36,6 +40,7 @@ export const useSmsWalletStore = defineStore('smsWallet', () => {
isBlocked.value = res.is_blocked
deposit.value = res.deposit ?? { ...EMPTY_DEPOSIT }
loaded.value = true
negativeWarningVisible.value = res.balance < 0
} catch {
// خطای موجودی نباید مزاحم کاربر شود؛ ویجت در صورت نبود داده مخفی می‌ماند
} finally {
@@ -43,6 +48,10 @@ export const useSmsWalletStore = defineStore('smsWallet', () => {
}
}
function dismissNegativeWarning() {
negativeWarningVisible.value = false
}
return {
balance,
isBlocked,
@@ -50,6 +59,8 @@ export const useSmsWalletStore = defineStore('smsWallet', () => {
loaded,
loading,
visible,
refresh
negativeWarningVisible,
refresh,
dismissNegativeWarning
}
})