feat(user): identity gate for wallet/gift card and custom nav icons
This commit is contained in:
@@ -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
|
||||
|
||||
Vendored
+1
@@ -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 |
@@ -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>
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user