feat(user): identity gate for wallet/gift card and custom nav icons

This commit is contained in:
2026-10-20 09:10:03 +03:30
parent 5e181fdae9
commit 7626453def
14 changed files with 104 additions and 3 deletions
@@ -3,6 +3,8 @@
فرمت هر خط:
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
- 2026-10-19 | قفل احراز واریز/هدیه | `useWalletPage` + `useGiftCardPage` + `assertCustomerIdentityVerified` روی withdraw و giftcard/use | is_legal=0 دیالوگ می‌دهد و API هم رد می‌کند
- 2026-10-19 | آیکون پشتیبانی/هشدار/قوانین | `assets/img/boursecu-support-icon.png` + `price-trigger` + `rules` و `navigation.ts` / `useUserPage.ts` | آدمک=پشتیبانی، زنگ دلار=هشدار قیمت سایدبار، برگه=قوانین
- 2026-09-08 | لیست کارت هدیه کاربر | `views/base/giftCard/` + `api/giftCard.ts` + سرور `GiftCardController::mine` / `GET customer/giftcard/mine` | زیر فرم: available (اختصاصی + عمومی use_once=0 تک‌کد) و history؛ تخفیف فقط نمایش/سبد نه giftcard/use
- 2026-09-08 | تب کارت هدیه/تخفیف در تراکنش‌ها | `transactions/config/boursecuTabs.ts` + `useTransactionsPageData.ts` + `Item.vue` | fetchMode giftcard/discount از historyِ mine؛ واحد تخفیف٪/طلا در Item
- 2026-09-08 | گیر کردن در لاگین بعد رفرش | `app/authBoot.ts` + `setupRouter.ts` | finger unlock در sessionStorage؛ بعد ۳دقیقه fingerprintChecked ریست؛ لاگین بدون userInfo نرود home
+1
View File
@@ -56,6 +56,7 @@ declare module 'vue' {
Format: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/format.vue')['default']
GoldDayCalculatorModal: typeof import('./src/components/GoldDayCalculatorModal.vue')['default']
GoldLoading: typeof import('./src/components/GoldLoading.vue')['default']
IdentityRequiredDialog: typeof import('./src/components/IdentityRequiredDialog.vue')['default']
InstallButton: typeof import('./src/components/InstallButton.vue')['default']
ListAnim: typeof import('./src/components/ListAnim.vue')['default']
Mode: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/mode.vue')['default']
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+46
View File
@@ -0,0 +1,46 @@
<template>
<van-dialog
v-model:show="open"
class="completeButtonStyle"
confirm-button-text="تکمیل اطلاعات"
show-confirm-button
@confirm="goProfile"
>
<div class="flex flex-col items-center p-2">
<BasicIcon color="orange" icon="vuesax-linear:info-circle" size="50" />
<div class="mt-2 text-center">{{ message }}</div>
</div>
</van-dialog>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import router from '@/router'
const props = withDefaults(
defineProps<{
show?: boolean
action?: string
}>(),
{ show: false, action: 'این کار' },
)
const emit = defineEmits<{
'update:show': [value: boolean]
}>()
const open = computed({
get: () => props.show,
set: (value: boolean) => emit('update:show', value),
})
const message = computed(
() =>
`برای ${props.action}، لطفا اطلاعات خود را تکمیل کنید یا در صورت تکمیل اطلاعات، منتظر تایید اطلاعات خود بمانید.`,
)
function goProfile() {
router.push({ name: 'user' })
}
</script>
+14 -2
View File
@@ -57,9 +57,21 @@ const BOURSECU_PRIMARY_NAV: AppNavItem[] = [
]
export const SECONDARY_NAV: AppNavItem[] = [
{ id: 'notifyTrigger', routeName: 'notifyTrigger', label: 'هشدار قیمت', icon: 'vuesax-linear:bill' },
{
id: 'notifyTrigger',
routeName: 'notifyTrigger',
label: 'هشدار قیمت',
icon: 'vuesax-linear:bill',
iconSrc: getBoursecuActionIconSrc('priceTrigger'),
},
{ id: 'about', routeName: 'about', label: 'درباره ما', icon: 'vuesax-linear:info-circle' },
{ id: 'rules', routeName: 'rules', label: 'قوانین و مقررات', icon: 'vuesax-linear:task' },
{
id: 'rules',
routeName: 'rules',
label: 'قوانین و مقررات',
icon: 'vuesax-linear:task',
iconSrc: getBoursecuActionIconSrc('rules'),
},
]
export function getPrimaryNavItems(): AppNavItem[] {
+6
View File
@@ -9,6 +9,9 @@ import boursecuBuySellIcon from '@/assets/img/boursecu-buy-sell-icon.png'
import boursecuAssetsIcon from '@/assets/img/boursecu-assets-icon.png'
import boursecuHomeIcon from '@/assets/img/boursecu-home-icon.png'
import boursecuSettingsIcon from '@/assets/img/boursecu-settings-icon.png'
import boursecuSupportIcon from '@/assets/img/boursecu-support-icon.png'
import boursecuPriceTriggerIcon from '@/assets/img/boursecu-price-trigger-icon.png'
import boursecuRulesIcon from '@/assets/img/boursecu-rules-icon.png'
export function getConfigApp(key: string) {
const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {}
@@ -77,6 +80,9 @@ export function getBoursecuActionIconSrc(actionId: string): string | undefined {
convert: boursecuConvertIcon,
home: boursecuHomeIcon,
settings: boursecuSettingsIcon,
support: boursecuSupportIcon,
priceTrigger: boursecuPriceTriggerIcon,
rules: boursecuRulesIcon,
}
return icons[actionId]
+4
View File
@@ -0,0 +1,4 @@
/** احراز نشده = is_legal صفر. ۱ و ۲ یعنی اطلاعات ثبت شده و معامله مجاز است. */
export function isCustomerIdentityVerified(isLegal: unknown): boolean {
return Number(isLegal ?? 0) !== 0
}
@@ -6,6 +6,8 @@ import {
type CustomerGiftCardItem,
} from '@/api/giftCard'
import { message } from 'ant-design-vue'
import { useUserStore } from '@/store/userStore'
import { isCustomerIdentityVerified } from '@/utils/customerIdentity'
export interface GiftCardPageContext {
form: Ref<{ code: string }>
@@ -17,6 +19,7 @@ export interface GiftCardPageContext {
listLoading: Ref<boolean>
loadMyGiftCards: () => Promise<void>
fillCode: (code: string) => void
identityRequiredVisible: Ref<boolean>
}
export const GIFT_CARD_PAGE_CONTEXT_KEY: InjectionKey<GiftCardPageContext> =
@@ -29,6 +32,8 @@ export function useGiftCardPage() {
const available = ref<CustomerGiftCardItem[]>([])
const history = ref<CustomerGiftCardItem[]>([])
const listLoading = ref(false)
const identityRequiredVisible = ref(false)
const userStore = useUserStore()
const disableButton = computed(() => {
if (!loading.value) {
@@ -56,6 +61,10 @@ export function useGiftCardPage() {
}
async function registerGiftCard() {
if (!isCustomerIdentityVerified(userStore.userInfo?.is_legal)) {
identityRequiredVisible.value = true
return
}
const code = form.value.code.trim()
if (!code) return
@@ -94,6 +103,7 @@ export function useGiftCardPage() {
listLoading,
loadMyGiftCards,
fillCode,
identityRequiredVisible,
}
provide(GIFT_CARD_PAGE_CONTEXT_KEY, context)
+3 -1
View File
@@ -2,6 +2,7 @@
<div class="gift-card-page-root">
<DesktopGiftCardView v-show="isDesktop" />
<MobileGiftCardView v-show="!isDesktop" />
<IdentityRequiredDialog v-model:show="identityRequiredVisible" action="ثبت کد هدیه" />
</div>
</template>
@@ -10,10 +11,11 @@ import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { useGiftCardPage } from './composables/useGiftCardPage'
import DesktopGiftCardView from './desktop/DesktopGiftCardView.vue'
import MobileGiftCardView from './mobile/MobileGiftCardView.vue'
import IdentityRequiredDialog from '@/components/IdentityRequiredDialog.vue'
const isDesktop = useDesktopBreakpoint()
useGiftCardPage()
const { identityRequiredVisible } = useGiftCardPage()
</script>
<style scoped>
@@ -157,6 +157,7 @@ export function useUserPage() {
id: 'support',
label: 'پشتیبانی',
icon: 'vuesax-linear:messages-2',
iconSrc: getBoursecuActionIconSrc('support'),
onClick: () => router.push({ name: 'support' }),
visible: isUserSupportChatEnabled(),
badge: unreadSupportCount.value > 0 ? unreadSupportCount.value : undefined,
@@ -187,6 +188,7 @@ export function useUserPage() {
id: 'rules',
label: 'قوانین و مقررات',
icon: 'vuesax-linear:task',
iconSrc: getBoursecuActionIconSrc('rules'),
onClick: () => router.push({ name: 'rules' }),
visible: true,
},
@@ -11,6 +11,7 @@ import { useInfiniteScroll } from '@vueuse/core'
import { message } from 'ant-design-vue'
import type { Card } from '@/models/wallet'
import { useUserStore } from '@/store/userStore'
import { isCustomerIdentityVerified } from '@/utils/customerIdentity'
import { useConfigStore } from '@/store/configStore'
import {
getWalletDepositQuickAmountsRial,
@@ -37,6 +38,7 @@ export interface WalletPageContext {
showConfirmCode: Ref<boolean>
withdrawInfModal: Ref<boolean>
infoVisible: Ref<boolean>
identityRequiredVisible: Ref<boolean>
refOtpInput: Ref<InstanceType<typeof OtpInput> | null>
form: Ref<Transaction>
extraNote: Ref<string>
@@ -84,6 +86,13 @@ export function useWalletPage() {
const chargeAccountModal = ref(false)
const showConfirmCode = ref(false)
const withdrawInfModal = ref(false)
const identityRequiredVisible = ref(false)
function ensureIdentity(): boolean {
if (isCustomerIdentityVerified(userStore.userInfo?.is_legal)) return true
identityRequiredVisible.value = true
return false
}
const infoVisible = ref(false)
const refOtpInput = ref<InstanceType<typeof OtpInput> | null>(null)
const mobileDepositScrollEl = ref<HTMLElement | null>(null)
@@ -190,6 +199,7 @@ export function useWalletPage() {
}
async function ok() {
if (!ensureIdentity()) return
try {
if (form.value.value !== undefined) {
loading.value = true
@@ -211,6 +221,7 @@ export function useWalletPage() {
}
async function handleOnComplete(value: string) {
if (!ensureIdentity()) return
try {
infoVisible.value = true
showConfirmCode.value = false
@@ -268,6 +279,7 @@ export function useWalletPage() {
showConfirmCode,
withdrawInfModal,
infoVisible,
identityRequiredVisible,
refOtpInput,
form,
extraNote,
+4
View File
@@ -78,6 +78,8 @@
<SkeletonLoading v-model:show="infoVisible" />
<IdentityRequiredDialog v-model:show="identityRequiredVisible" action="واریز یا برداشت" />
<WalletDepositGuideModal
v-model:visible="chargeAccountModal"
:support-link="depositGuideSupportLink"
@@ -117,6 +119,7 @@ import MobileWalletView from './mobile/MobileWalletView.vue'
import SkeletonLoading from './components/SkeletonLoading.vue'
import WalletAddCardForm from './components/WalletAddCardForm.vue'
import WalletDepositGuideModal from './components/WalletDepositGuideModal.vue'
import IdentityRequiredDialog from '@/components/IdentityRequiredDialog.vue'
const isDesktop = useDesktopBreakpoint()
@@ -126,6 +129,7 @@ const {
showConfirmCode,
withdrawInfModal,
infoVisible,
identityRequiredVisible,
refOtpInput,
extraNote,
supportMessengerText,