fix(user): register dialog, KYC modal, and home loading gaps
Repair gift-card register success modal text, enforce KYC with correct identity checks, and show skeleton/fallback while home layout loads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# یادداشتهای یادگیری — abshode-domain-lookup
|
# یادداشتهای یادگیری — abshode-domain-lookup
|
||||||
|
|
||||||
|
- 2026-11-10 | مدال موفقیت ثبتنام خالی | `DialogVantOneButton.vue` | تم لاگین `color:#fff`؛ بدون teleport متن سفید روی دیالوگ سفید — مثل `DialogVant` با teleport+رنگ تیره
|
||||||
|
- 2026-11-10 | خانه خالی بدون KYC | `RequiredKycModal` + `HomeLayoutRenderer` | v-model:open روی computed فقطخواندنی؛ تشخیص احراز با `isCustomerIdentityVerified`؛ skeleton هنگام lazy layout
|
||||||
|
|
||||||
فرمت هر خط:
|
فرمت هر خط:
|
||||||
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -169,6 +169,7 @@ import {
|
|||||||
useChatService,
|
useChatService,
|
||||||
} from '@/utils/chat'
|
} from '@/utils/chat'
|
||||||
import { getToken } from '@/utils'
|
import { getToken } from '@/utils'
|
||||||
|
import { isCustomerIdentityVerified } from '@/utils/customerIdentity'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { showBrowserNotification } from '@/utils/browserNotification'
|
import { showBrowserNotification } from '@/utils/browserNotification'
|
||||||
|
|
||||||
@@ -329,7 +330,10 @@ watch(
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const isKycRequired = computed(
|
const isKycRequired = computed(
|
||||||
() => Boolean(getToken()) && (userStore.userInfo?.is_legal === 0 || !userStore.userInfo?.is_legal),
|
() =>
|
||||||
|
Boolean(getToken()) &&
|
||||||
|
Boolean(userStore.userInfo) &&
|
||||||
|
!isCustomerIdentityVerified(userStore.userInfo?.is_legal),
|
||||||
)
|
)
|
||||||
|
|
||||||
const giftCardFabAccent = computed(
|
const giftCardFabAccent = computed(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useAuthStore } from '@/store/useAuthStore'
|
|||||||
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
|
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
|
||||||
import { getFingerPosition, setFingerPosition } from '@/app/authBoot'
|
import { getFingerPosition, setFingerPosition } from '@/app/authBoot'
|
||||||
import { isGuestHomeQuery } from '@/utils/guestHome'
|
import { isGuestHomeQuery } from '@/utils/guestHome'
|
||||||
|
import { isCustomerIdentityVerified } from '@/utils/customerIdentity'
|
||||||
import type { RouteLocationNormalized } from 'vue-router'
|
import type { RouteLocationNormalized } from 'vue-router'
|
||||||
|
|
||||||
const PUBLIC_ROUTE_NAMES = ['login', '404', 'educate', 'aboutUs', 'aboutSoftware', 'tvMonitor'] as const
|
const PUBLIC_ROUTE_NAMES = ['login', '404', 'educate', 'aboutUs', 'aboutSoftware', 'tvMonitor'] as const
|
||||||
@@ -85,7 +86,7 @@ export function setupRouterGuards() {
|
|||||||
// کاربر احراز هویت نشده نباید بتواند به صفحات داخلی دسترسی پیدا کند
|
// کاربر احراز هویت نشده نباید بتواند به صفحات داخلی دسترسی پیدا کند
|
||||||
if (
|
if (
|
||||||
userStore.userInfo &&
|
userStore.userInfo &&
|
||||||
(userStore.userInfo.is_legal === 0 || !userStore.userInfo.is_legal) &&
|
!isCustomerIdentityVerified(userStore.userInfo.is_legal) &&
|
||||||
!isPublicRoute(to) &&
|
!isPublicRoute(to) &&
|
||||||
to.name !== 'home'
|
to.name !== 'home'
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,61 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <div v-if="modelValue">-->
|
<van-dialog
|
||||||
<van-dialog class="oneButtonDialog">
|
v-model:show="show"
|
||||||
<div class="my-4">
|
class="oneButtonDialog"
|
||||||
|
teleport="body"
|
||||||
|
:close-on-click-overlay="closeOnClickOverlay"
|
||||||
|
:z-index="zIndex"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<div class="oneButtonDialog__body my-4 text-slate-800">
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
<slot name="img">{{ text2 }}</slot>
|
<slot name="img">{{ text2 }}</slot>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-center text-[18px] font-semibold">
|
<div class="w-full flex justify-center text-[18px] font-semibold text-slate-800">
|
||||||
<slot name="title">{{ text1 }}</slot>
|
<slot name="title">{{ text1 }}</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class=" border-t border-solid border-[#E5E7EB] pt-4 mx-2 mt-6 font-semibold text-[15px]">
|
<div
|
||||||
|
class="border-t border-solid border-[#E5E7EB] pt-4 mx-2 mt-6 font-semibold text-[15px] text-slate-800"
|
||||||
|
>
|
||||||
<slot name="button">{{ text3 }}</slot>
|
<slot name="button">{{ text3 }}</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</van-dialog>
|
</van-dialog>
|
||||||
<!-- <van-dialog style="background-color: #2b3f6e" show-cancel-button cancelButtonText="بیخیال" confirmButtonText="بله" cancelButtonColor="">-->
|
|
||||||
<!-- <div class="w-full mt-6 text-white ">-->
|
|
||||||
<!-- <div class="w-full flex mb-[10px] ">-->
|
|
||||||
<!-- <Icon icon="vuesax-linear:info-circle" class="mx-4" size="25"/>-->
|
|
||||||
<!-- <span class="text-lg flex-1">-->
|
|
||||||
<!-- <slot name="title">{{text1}}</slot>-->
|
|
||||||
<!-- </span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <div class="flex justify-start mb-6 mt-8" style="font-weight:700;">-->
|
|
||||||
<!-- <slot name="button">{{text2}}</slot>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </van-dialog>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import { APP_Z_MODAL } from '@/utils/zIndex'
|
||||||
|
|
||||||
// import Icon from "/@/assets/icons/Icon.vue";
|
defineOptions({ inheritAttrs: false })
|
||||||
import {ref, watch} from 'vue';
|
|
||||||
|
|
||||||
const show = ref(true);
|
const props = withDefaults(
|
||||||
const props = withDefaults(defineProps<{
|
defineProps<{
|
||||||
// modelValue: boolean,
|
show?: boolean
|
||||||
timeOut: number,
|
timeOut?: number
|
||||||
text1?: string,
|
text1?: string
|
||||||
text2?: string,
|
text2?: string
|
||||||
text3?: string
|
text3?: string
|
||||||
// top?: string,
|
closeOnClickOverlay?: boolean
|
||||||
|
zIndex?: number
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
show: false,
|
||||||
|
timeOut: 3000,
|
||||||
|
closeOnClickOverlay: true,
|
||||||
|
zIndex: APP_Z_MODAL,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
}>(), {})
|
const emit = defineEmits<{
|
||||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
(e: 'update:show', value: boolean): void
|
||||||
watch(() => props.modelValue, (value) => {
|
}>()
|
||||||
if (props.timeOut !== -1) showNotify()
|
|
||||||
|
const show = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (value: boolean) => emit('update:show', value),
|
||||||
})
|
})
|
||||||
const showNotify = () => {
|
|
||||||
console.log('notify', show.value)
|
watch(
|
||||||
setTimeout(() => {
|
() => props.show,
|
||||||
emit('update:modelValue', false)
|
(value) => {
|
||||||
}, props.timeOut);
|
if (value && props.timeOut !== -1) {
|
||||||
};
|
setTimeout(() => {
|
||||||
|
emit('update:show', false)
|
||||||
|
}, props.timeOut)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@@ -67,17 +77,13 @@ const showNotify = () => {
|
|||||||
animation: notify-in 0.2s reverse;
|
animation: notify-in 0.2s reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oneButtonDialog .van-dialog__confirm, .van-dialog__cancel {
|
.oneButtonDialog .van-dialog__confirm,
|
||||||
|
.oneButtonDialog .van-dialog__cancel {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.van-dialog__confirm, .van-dialog__cancel {
|
|
||||||
//background-color: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oneButtonDialog .van-dialog__confirm .van-button__text {
|
.oneButtonDialog .van-dialog__confirm .van-button__text {
|
||||||
color: #1C2D56;
|
color: #1c2d56;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
@@ -87,4 +93,8 @@ const showNotify = () => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oneButtonDialog__body {
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:open="isModalVisible"
|
:open="requiresKyc"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
:keyboard="false"
|
:keyboard="false"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
:centered="true"
|
:centered="true"
|
||||||
:z-index="5500"
|
:z-index="APP_Z_MODAL"
|
||||||
:width="modalWidth"
|
:width="modalWidth"
|
||||||
|
:get-container="getModalContainer"
|
||||||
class="required-kyc-modal"
|
class="required-kyc-modal"
|
||||||
wrap-class-name="required-kyc-modal-wrap"
|
wrap-class-name="required-kyc-modal-wrap"
|
||||||
>
|
>
|
||||||
@@ -206,7 +207,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import type { FormInstance } from 'ant-design-vue'
|
import type { FormInstance } from 'ant-design-vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
@@ -221,6 +222,8 @@ import { isValidPersian, requireNationalCode, required } from '@/utils/inputRule
|
|||||||
import { normalizeAuthFormFields } from '@/utils/persianDigits'
|
import { normalizeAuthFormFields } from '@/utils/persianDigits'
|
||||||
import { performUserLogout } from '@/utils/authSession'
|
import { performUserLogout } from '@/utils/authSession'
|
||||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||||
|
import { isCustomerIdentityVerified } from '@/utils/customerIdentity'
|
||||||
|
import { APP_Z_MODAL, getModalContainer } from '@/utils/zIndex'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -241,22 +244,22 @@ const formState = reactive({
|
|||||||
|
|
||||||
const modalWidth = computed(() => (isDesktop.value ? '460px' : '92vw'))
|
const modalWidth = computed(() => (isDesktop.value ? '460px' : '92vw'))
|
||||||
|
|
||||||
const isModalVisible = computed(() => {
|
const requiresKyc = computed(() => {
|
||||||
if (!getToken()) return false
|
if (!getToken()) return false
|
||||||
if (route.name === 'login' || route.name === 'tvMonitor' || route.name === 'aboutSoftware') {
|
if (route.name === 'login' || route.name === 'tvMonitor' || route.name === 'aboutSoftware') {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const info = userStore.userInfo
|
const info = userStore.userInfo
|
||||||
if (!info) return false
|
if (!info) return false
|
||||||
// 0 indicates unverified. 1 and 2 indicate verified customer.
|
return !isCustomerIdentityVerified(info.is_legal)
|
||||||
return info.is_legal === 0 || !info.is_legal
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isPendingApproval = computed(() => {
|
const isPendingApproval = computed(() => {
|
||||||
const info = userStore.userInfo
|
const info = userStore.userInfo
|
||||||
if (!info) return false
|
if (!info) return false
|
||||||
// If national code is set and user is still unverified (is_legal === 0)
|
return Boolean(
|
||||||
return Boolean(info.n_code && info.is_legal === 0)
|
String(info.n_code ?? '').trim() && !isCustomerIdentityVerified(info.is_legal),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function populateFormFromUser() {
|
function populateFormFromUser() {
|
||||||
@@ -295,8 +298,7 @@ async function onSubmit() {
|
|||||||
const res: any = await updateMe(payload)
|
const res: any = await updateMe(payload)
|
||||||
await userStore.getMyInfo()
|
await userStore.getMyInfo()
|
||||||
|
|
||||||
const currentLegal = userStore.userInfo?.is_legal
|
if (isCustomerIdentityVerified(userStore.userInfo?.is_legal)) {
|
||||||
if (currentLegal === 1 || currentLegal === 2) {
|
|
||||||
message.success('احراز هویت شما با موفقیت انجام و تأیید شد.')
|
message.success('احراز هویت شما با موفقیت انجام و تأیید شد.')
|
||||||
isEditing.value = false
|
isEditing.value = false
|
||||||
} else {
|
} else {
|
||||||
@@ -317,8 +319,7 @@ async function onCheckStatus() {
|
|||||||
try {
|
try {
|
||||||
checkingStatus.value = true
|
checkingStatus.value = true
|
||||||
await userStore.getMyInfo()
|
await userStore.getMyInfo()
|
||||||
const currentLegal = userStore.userInfo?.is_legal
|
if (isCustomerIdentityVerified(userStore.userInfo?.is_legal)) {
|
||||||
if (currentLegal === 1 || currentLegal === 2) {
|
|
||||||
message.success('حساب کاربری شما تأیید شد!')
|
message.success('حساب کاربری شما تأیید شد!')
|
||||||
} else {
|
} else {
|
||||||
message.info('اطلاعات شما همچنان در صف بررسی و تایید مدیریت است.')
|
message.info('اطلاعات شما همچنان در صف بررسی و تایید مدیریت است.')
|
||||||
@@ -333,8 +334,22 @@ async function onCheckStatus() {
|
|||||||
async function onLogout() {
|
async function onLogout() {
|
||||||
await performUserLogout()
|
await performUserLogout()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (getToken() && !userStore.userInfo) {
|
||||||
|
void userStore.getMyInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.required-kyc-modal-wrap {
|
||||||
|
.ant-modal-content {
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.kyc-modal-content {
|
.kyc-modal-content {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import HomeSkeleton from '../components/HomeSkeleton.vue'
|
|||||||
import { getHomeLayoutConfig } from './registry'
|
import { getHomeLayoutConfig } from './registry'
|
||||||
import { HOME_SECTION_COMPONENTS } from './sections'
|
import { HOME_SECTION_COMPONENTS } from './sections'
|
||||||
import type { HomeLayoutConfig, HomeSectionId } from './types'
|
import type { HomeLayoutConfig, HomeSectionId } from './types'
|
||||||
|
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
layoutConfig?: HomeLayoutConfig
|
layoutConfig?: HomeLayoutConfig
|
||||||
@@ -43,6 +44,11 @@ const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
|
|||||||
function resolveSectionComponent(sectionId: HomeSectionId) {
|
function resolveSectionComponent(sectionId: HomeSectionId) {
|
||||||
return HOME_SECTION_COMPONENTS[sectionId]
|
return HOME_SECTION_COMPONENTS[sectionId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function retryLoad() {
|
||||||
|
if (!getToken()) return
|
||||||
|
await userStore.getMyInfo()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -77,4 +83,13 @@ function resolveSectionComponent(sectionId: HomeSectionId) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HomeSkeleton v-else-if="showSkeleton" class="w-full" />
|
<HomeSkeleton v-else-if="showSkeleton" class="w-full" />
|
||||||
|
|
||||||
|
<div v-else class="home-layout-fallback flex flex-col items-center justify-center gap-3 p-6 text-center min-h-[50vh]">
|
||||||
|
<p class="text-sm text-slate-600 leading-7">
|
||||||
|
در حال آمادهسازی صفحه اصلی هستیم. اگر چیزی نمایش داده نمیشود، یکبار تلاش مجدد کنید.
|
||||||
|
</p>
|
||||||
|
<BasicButton type="primary" class="!rounded-xl" @click="retryLoad">
|
||||||
|
تلاش مجدد
|
||||||
|
</BasicButton>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineAsyncComponent, type Component } from 'vue'
|
import { defineAsyncComponent, type Component } from 'vue'
|
||||||
import type { HomeLayoutConfig, HomeLayoutId } from './types'
|
import type { HomeLayoutConfig, HomeLayoutId } from './types'
|
||||||
import { boursecuHomeLayoutConfig } from './configs/boursecu'
|
import { boursecuHomeLayoutConfig } from './configs/boursecu'
|
||||||
|
import HomeSkeleton from '../components/HomeSkeleton.vue'
|
||||||
|
|
||||||
export const DEFAULT_HOME_LAYOUT_ID: HomeLayoutId = 'boursecu'
|
export const DEFAULT_HOME_LAYOUT_ID: HomeLayoutId = 'boursecu'
|
||||||
|
|
||||||
@@ -12,7 +13,11 @@ let cachedLayoutRenderer: Component | null = null
|
|||||||
|
|
||||||
function getLayoutRenderer(): Component {
|
function getLayoutRenderer(): Component {
|
||||||
if (!cachedLayoutRenderer) {
|
if (!cachedLayoutRenderer) {
|
||||||
cachedLayoutRenderer = defineAsyncComponent(() => import('./HomeLayoutRenderer.vue'))
|
cachedLayoutRenderer = defineAsyncComponent({
|
||||||
|
loader: () => import('./HomeLayoutRenderer.vue'),
|
||||||
|
loadingComponent: HomeSkeleton,
|
||||||
|
delay: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedLayoutRenderer
|
return cachedLayoutRenderer
|
||||||
|
|||||||
Reference in New Issue
Block a user