feat: add required kyc blocking modal and fix copper asset units to kilogram

This commit is contained in:
2026-11-05 17:17:29 +03:30
parent ae01f6c8d6
commit 0f50ec315c
18 changed files with 495 additions and 50 deletions
+1
View File
@@ -68,6 +68,7 @@ declare module 'vue' {
PwaInstallGuideModal: typeof import('./src/components/pwa/PwaInstallGuideModal.vue')['default']
PwaInstallStepIllustration: typeof import('./src/components/pwa/PwaInstallStepIllustration.vue')['default']
RangePicker: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/range-picker.vue')['default']
RequiredKycModal: typeof import('./src/components/RequiredKycModal.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectInRange: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/select-in-range.vue')['default']
+6 -1
View File
@@ -30,9 +30,10 @@
<component :is="Component" :key="route.fullPath" />
</router-view>
</div>
<RequiredKycModal v-if="!showSplash && !isTvBoardRoute && !isLoginRoute" />
</AConfigProvider>
<div
v-if="showSupportButton && !showSplash && route.name !== 'support' && route.name !== 'login' && !isTvBoardRoute"
v-if="showSupportButton && !showSplash && route.name !== 'support' && route.name !== 'login' && !isTvBoardRoute && !isKycRequired"
id="support-button"
ref="supportButtonEl"
class="fixed z-[5100] cursor-grab select-none rounded-full"
@@ -132,6 +133,7 @@ import { usePwaInstall } from '@/composables/usePwaInstall'
import PwaInstallBanner from '@/components/pwa/PwaInstallBanner.vue'
import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue'
import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue'
import RequiredKycModal from '@/components/RequiredKycModal.vue'
import { useUserStore } from '@/store/userStore'
import { useSettingsStore } from '@/store/settingsStore'
import {
@@ -299,6 +301,9 @@ watch(
)
const userStore = useUserStore()
const isKycRequired = computed(
() => Boolean(getToken()) && (userStore.userInfo?.is_legal === 0 || !userStore.userInfo?.is_legal),
)
const settingsStore = useSettingsStore()
const showLoginCreditExtras = computed(() => {
if (!isLoginRoute.value) return false
+10
View File
@@ -82,6 +82,16 @@ export function setupRouterGuards() {
return goToLogin(next)
}
// کاربر احراز هویت نشده نباید بتواند به صفحات داخلی دسترسی پیدا کند
if (
userStore.userInfo &&
(userStore.userInfo.is_legal === 0 || !userStore.userInfo.is_legal) &&
!isPublicRoute(to) &&
to.name !== 'home'
) {
return goToHome(next)
}
if (to.name === 'notifyTrigger') {
const { useSettingsStore } = await import('@/store/settingsStore')
const settingsStore = useSettingsStore()
+352
View File
@@ -0,0 +1,352 @@
<template>
<a-modal
v-model:open="isModalVisible"
:closable="false"
:mask-closable="false"
:keyboard="false"
:footer="null"
:centered="true"
:z-index="5500"
:width="modalWidth"
class="required-kyc-modal"
wrap-class-name="required-kyc-modal-wrap"
>
<div class="kyc-modal-content text-right" dir="rtl">
<!-- State 1: Need to enter/edit KYC information -->
<template v-if="!isPendingApproval || isEditing">
<div class="text-center mb-5">
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-amber-50 text-amber-700 border border-amber-200 mb-3 shadow-sm">
<BasicIcon icon="vuesax-bold:security-user" size="32" color="#b45309" />
</div>
<h2 class="text-lg font-bold text-slate-800 mb-1">
احراز هویت الزامی است
</h2>
<p class="text-xs text-slate-500 leading-6 max-w-sm mx-auto">
کاربر گرامی، جهت حفظ امنیت حساب و امکان انجام معاملات مس و واریز/برداشت وجه، لازم است مشخصات هویتی خود را ثبت نمایید.
</p>
</div>
<a-form
ref="formRef"
:model="formState"
layout="vertical"
class="kyc-form space-y-2"
@finish="onSubmit"
>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-3 gap-y-0">
<a-form-item
label="نام"
name="fname"
:rules="[required('نام'), isValidPersian()]"
>
<BasicInput
v-model:value="formState.fname"
placeholder="مثال: علی"
:disabled="loading"
/>
</a-form-item>
<a-form-item
label="نام خانوادگی"
name="lname"
:rules="[required('نام خانوادگی'), isValidPersian()]"
>
<BasicInput
v-model:value="formState.lname"
placeholder="مثال: محمدی"
:disabled="loading"
/>
</a-form-item>
</div>
<a-form-item
label="کد ملی (۱۰ رقم)"
name="n_code"
:rules="[requireNationalCode()]"
>
<BasicInput
v-model:value="formState.n_code"
type="text"
inputmode="numeric"
maxlength="10"
placeholder="مثال: ۰۰۱۲۳۴۵۶۷۸"
:disabled="loading"
/>
</a-form-item>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-3 gap-y-0">
<a-form-item
label="تاریخ تولد"
name="birth_date"
:rules="[required('تاریخ تولد')]"
>
<BaseDatePicker
v-model:value="formState.birth_date"
class="w-full"
input-read-only
placeholder="انتخاب تاریخ تولد"
:disabled="loading"
/>
</a-form-item>
<a-form-item
label="شهر"
name="city"
:rules="[required('شهر')]"
>
<BasicInput
v-model:value="formState.city"
placeholder="مثال: تهران"
:disabled="loading"
/>
</a-form-item>
</div>
<div class="pt-4 flex flex-col gap-2.5">
<BasicButton
type="primary"
html-type="submit"
:disabled="loading"
class="w-full h-11 text-sm font-semibold !rounded-xl"
>
<a-spin v-if="loading" size="small" />
<span v-else>ثبت و استعلام هویت</span>
</BasicButton>
<div class="flex items-center justify-between gap-2 pt-1">
<button
v-if="isEditing && isPendingApproval"
type="button"
class="text-xs text-slate-500 hover:text-slate-700 py-1 px-2 cursor-pointer transition-colors"
@click="isEditing = false"
>
انصراف از ویرایش
</button>
<button
type="button"
class="text-xs text-red-600 hover:text-red-700 flex items-center gap-1 py-1 px-2 mr-auto cursor-pointer transition-colors"
@click="onLogout"
>
<BasicIcon icon="vuesax-linear:logout" size="14" color="#dc2626" />
<span>خروج از حساب کاربری</span>
</button>
</div>
</div>
</a-form>
</template>
<!-- State 2: Already submitted, waiting for Admin review -->
<template v-else>
<div class="text-center mb-5">
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-amber-50 text-amber-700 border border-amber-200 mb-3 shadow-sm">
<BasicIcon icon="vuesax-bold:clock" size="32" color="#d97706" />
</div>
<h2 class="text-lg font-bold text-slate-800 mb-1">
در انتظار تأیید اطلاعات هویتی
</h2>
<p class="text-xs text-slate-600 leading-6 max-w-sm mx-auto">
اطلاعات شما با موفقیت در سامانه ثبت شده و در صف بررسی و تایید کارشناسان مدیریت قرار دارد. پس از تایید هویت، دسترسی به تمام بخش‌ها فعال خواهد شد.
</p>
</div>
<div class="bg-slate-50 rounded-xl p-3.5 border border-slate-200 text-xs text-slate-700 space-y-2 mb-5">
<div class="flex justify-between items-center py-1 border-b border-slate-200/60">
<span class="text-slate-500">نام و نام خانوادگی:</span>
<span class="font-medium text-slate-800">{{ formState.fname }} {{ formState.lname }}</span>
</div>
<div class="flex justify-between items-center py-1 border-b border-slate-200/60">
<span class="text-slate-500">کد ملی:</span>
<span class="font-medium text-slate-800 tracking-wider">{{ formState.n_code }}</span>
</div>
<div v-if="formState.birth_date" class="flex justify-between items-center py-1 border-b border-slate-200/60">
<span class="text-slate-500">تاریخ تولد:</span>
<span class="font-medium text-slate-800">{{ formState.birth_date }}</span>
</div>
<div v-if="formState.city" class="flex justify-between items-center py-1">
<span class="text-slate-500">شهر:</span>
<span class="font-medium text-slate-800">{{ formState.city }}</span>
</div>
</div>
<div class="flex flex-col gap-2.5">
<BasicButton
type="primary"
:disabled="checkingStatus"
class="w-full h-11 text-sm font-semibold !rounded-xl"
@click="onCheckStatus"
>
<a-spin v-if="checkingStatus" size="small" />
<span v-else>بررسی مجدد وضعیت تأیید</span>
</BasicButton>
<div class="flex items-center justify-between gap-2 pt-2">
<button
type="button"
class="text-xs text-slate-600 hover:text-slate-800 flex items-center gap-1 py-1 px-2 cursor-pointer transition-colors"
@click="isEditing = true"
>
<BasicIcon icon="vuesax-linear:edit-2" size="14" color="#475569" />
<span>ویرایش مجدد اطلاعات</span>
</button>
<button
type="button"
class="text-xs text-red-600 hover:text-red-700 flex items-center gap-1 py-1 px-2 mr-auto cursor-pointer transition-colors"
@click="onLogout"
>
<BasicIcon icon="vuesax-linear:logout" size="14" color="#dc2626" />
<span>خروج از حساب</span>
</button>
</div>
</div>
</template>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import type { FormInstance } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { useUserStore } from '@/store/userStore'
import { updateMe } from '@/api/me'
import { getToken } from '@/utils'
import { isValidPersian, requireNationalCode, required } from '@/utils/inputRules'
import { normalizeAuthFormFields } from '@/utils/persianDigits'
import { performUserLogout } from '@/utils/authSession'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
const route = useRoute()
const userStore = useUserStore()
const isDesktop = useDesktopBreakpoint()
const formRef = ref<FormInstance>()
const loading = ref(false)
const checkingStatus = ref(false)
const isEditing = ref(false)
const formState = reactive({
fname: '',
lname: '',
n_code: '',
birth_date: '',
city: '',
})
const modalWidth = computed(() => (isDesktop.value ? '460px' : '92vw'))
const isModalVisible = computed(() => {
if (!getToken()) return false
if (route.name === 'login' || route.name === 'tvMonitor' || route.name === 'aboutSoftware') {
return false
}
const info = userStore.userInfo
if (!info) return false
// 0 indicates unverified. 1 and 2 indicate verified customer.
return info.is_legal === 0 || !info.is_legal
})
const isPendingApproval = computed(() => {
const info = userStore.userInfo
if (!info) return false
// If national code is set and user is still unverified (is_legal === 0)
return Boolean(info.n_code && info.is_legal === 0)
})
function populateFormFromUser() {
const info = userStore.userInfo
if (!info) return
formState.fname = info.fname || ''
formState.lname = info.lname || ''
formState.n_code = info.n_code || ''
formState.birth_date = info.birth_date || ''
formState.city = info.city || ''
}
watch(
() => userStore.userInfo,
() => {
populateFormFromUser()
},
{ immediate: true },
)
async function onSubmit() {
try {
await formRef.value?.validateFields()
loading.value = true
normalizeAuthFormFields(formState)
const payload: any = {
...userStore.userInfo,
fname: formState.fname,
lname: formState.lname,
n_code: formState.n_code,
birth_date: formState.birth_date,
city: formState.city,
}
const res: any = await updateMe(payload)
await userStore.getMyInfo()
const currentLegal = userStore.userInfo?.is_legal
if (currentLegal === 1 || currentLegal === 2) {
message.success('احراز هویت شما با موفقیت انجام و تأیید شد.')
isEditing.value = false
} else {
isEditing.value = false
message.info(res?.message || 'اطلاعات هویتی شما ثبت شد و در انتظار تأیید مدیریت قرار گرفت.')
}
} catch (error: any) {
const errorMsg = error?.response?.data?.message || error?.message
if (errorMsg) {
message.error(errorMsg)
}
} finally {
loading.value = false
}
}
async function onCheckStatus() {
try {
checkingStatus.value = true
await userStore.getMyInfo()
const currentLegal = userStore.userInfo?.is_legal
if (currentLegal === 1 || currentLegal === 2) {
message.success('حساب کاربری شما تأیید شد!')
} else {
message.info('اطلاعات شما همچنان در صف بررسی و تایید مدیریت است.')
}
} catch (error) {
message.error('خطا در بررسی وضعیت. لطفاً مجدداً تلاش کنید.')
} finally {
checkingStatus.value = false
}
}
async function onLogout() {
await performUserLogout()
}
</script>
<style scoped>
.kyc-modal-content {
font-family: inherit;
}
:deep(.ant-form-item) {
margin-bottom: 12px;
}
:deep(.ant-form-item-label > label) {
font-size: 13px;
font-weight: 500;
color: #334155;
}
</style>
+43
View File
@@ -0,0 +1,43 @@
import router from '@/router'
import { removeToken } from '@/utils'
import { useUserStore } from '@/store/userStore'
import { useWalletStore } from '@/store/walletStore'
import { useWalletAssets } from '@/composables/useWalletAssets'
import { resetHomePageData } from '@/views/base/home/composables/useHomePageData'
import { resetWalletPageData } from '@/views/base/wallet/composables/useWalletPageData'
import { resetAddressPageData } from '@/views/base/address/composables/useAddressPageData'
import { resetTransactionsPageData } from '@/views/base/transactions/composables/useTransactionsPageData'
import { resetProductsPageData } from '@/views/base/products/composables/useProductsPageData'
export async function performUserLogout() {
try {
const userStore = useUserStore()
const walletStore = useWalletStore()
userStore.userInfo = undefined
walletStore.walletInfo = undefined
try {
const { clearWalletAssets } = useWalletAssets()
clearWalletAssets()
} catch {
// ignore
}
try { resetHomePageData() } catch {}
try { resetWalletPageData() } catch {}
try { resetAddressPageData() } catch {}
try { resetTransactionsPageData() } catch {}
try { resetProductsPageData() } catch {}
} catch (e) {
console.error('Logout cleanup error:', e)
}
removeToken()
try {
await router.push({ name: 'login' })
} catch {
// ignore
}
window.location.reload()
}
+1 -1
View File
@@ -7,7 +7,7 @@ interface BankDefinition {
aliases: string[]
}
const bankLogoModules = import.meta.glob<string>('@/assets/img/banks/*.{png,svg}', {
const bankLogoModules = import.meta.glob<string>('../assets/img/banks/*.{png,svg}', {
eager: true,
import: 'default',
})
+21 -9
View File
@@ -186,6 +186,14 @@ function sumBoursecuHomeProductInventories(
return sum
}
export function resolveBoursecuCopperUnit(unit?: string | null): string {
const trimmed = (unit ?? '').trim()
if (!trimmed || trimmed === 'گرم' || trimmed === 'gram' || trimmed === 'g') {
return 'کیلوگرم'
}
return trimmed
}
function findMetalTotalById(metalTotals: WalletMetalTotal[], metalId: number) {
return metalTotals.find((metal) => metal.metal_id === metalId)
}
@@ -195,7 +203,8 @@ function formatBoursecuMetalRowValue(metal: WalletMetalTotal): string {
if (metal.metal_id === METAL_GOLD) {
return `${amount} عدد`
}
return `${amount}${metal.unit ?? ''}`
const unit = metal.metal_id === METAL_COPPER ? resolveBoursecuCopperUnit(metal.unit) : (metal.unit ?? 'کیلوگرم')
return `${amount} ${unit}`
}
function formatRialNumber(value: number): string {
@@ -237,8 +246,11 @@ function buildBoursecuCopperPopupLines(summary: {
const metalTotals = summary.metal_totals ?? []
const copperMetal = findMetalTotalById(metalTotals, METAL_COPPER)
const shamshMetal = findMetalTotalById(metalTotals, METAL_GOLD)
const weightUnit = copperMetal?.unit || 'کیلوگرم'
const totalWeight = sumBoursecuHomeProductInventories(inventoryCards, countProducts)
const weightUnit = resolveBoursecuCopperUnit(copperMetal?.unit)
const totalWeight =
copperMetal != null && Number(copperMetal.total_weight ?? 0) > 0
? Number(copperMetal.total_weight)
: sumBoursecuHomeProductInventories(inventoryCards, countProducts)
const katodCard = findBoursecuProductCard(inventoryCards, countProducts, isBoursecuKatodCard)
const maftolCard = findBoursecuProductCard(inventoryCards, countProducts, isBoursecuMaftolCard)
@@ -319,10 +331,10 @@ export function buildBoursecuHomeAssetRows(
const countProducts = summary.count_products ?? []
const metalTotals = summary.metal_totals ?? []
const copperMetal = findMetalTotalById(metalTotals, METAL_COPPER)
const weightUnit = copperMetal?.unit || 'کیلوگرم'
const weightUnit = resolveBoursecuCopperUnit(copperMetal?.unit)
const totalCopperWeight =
copperMetal != null
? Number(copperMetal.total_weight ?? 0)
copperMetal != null && Number(copperMetal.total_weight ?? 0) > 0
? Number(copperMetal.total_weight)
: sumBoursecuHomeProductInventories(inventoryCards, countProducts)
const cash = Number(summary.cash_wallet ?? 0)
@@ -387,7 +399,7 @@ export function resolveSummaryMetalWeight(
/** متن توضیح مودال دارایی فلزی — بر اساس metal_totals سرور، بدون هاردکد طلا */
export function formatMetalAssetsInfoText(metal: WalletMetalTotal): string {
const unit = metal.unit || 'گرم'
const unit = metal.metal_id === METAL_COPPER ? resolveBoursecuCopperUnit(metal.unit) : (metal.unit || 'کیلوگرم')
return `${metal.label}: مجموع کیف محصولات (${metal.product_weight} ${unit}) + موجودی کیف اصلی (${metal.melted_weight} ${unit})`
}
@@ -510,7 +522,7 @@ export function computeMetalRialValue(
const metalId = Number(metal.metal_id ?? 0)
const unitPrice =
livePricesByMetal?.[metalId] ??
(metalId === METAL_GOLD ? liveGoldPrice : 0)
(metalId === METAL_COPPER || metalId === METAL_GOLD ? liveGoldPrice : 0)
return sum + weight * unitPrice
}, 0)
@@ -534,7 +546,7 @@ export function formatInventoryCardValue(card: {
display_type?: string
}): string {
const quantity = Number(card.quantity ?? 0)
const unit = card.unit ?? (card.display_type === 'count' ? 'عدد' : 'گرم')
const unit = card.unit ?? (card.display_type === 'count' ? 'عدد' : 'کیلوگرم')
const formatted =
card.display_type === 'count'
? usePriceWithoutType(quantity, false)
+4 -1
View File
@@ -6,8 +6,11 @@ export function isKilogramUnit(unit?: string | null) {
return value === TRADE_WEIGHT_UNIT_KILOGRAM || value === 'kilogram' || value === 'kg'
}
/** واحد نمایش از ستینگ — خالی یا نامعتبر = گرم */
/** واحد نمایش از ستینگ — در بورس مس پیش‌فرض همیشه کیلوگرم است */
export function resolveTradeWeightUnit(settingValue?: string | number | null) {
if (!settingValue) {
return TRADE_WEIGHT_UNIT_KILOGRAM
}
return isKilogramUnit(settingValue?.toString()) ? TRADE_WEIGHT_UNIT_KILOGRAM : TRADE_WEIGHT_UNIT_GRAM
}
+11 -5
View File
@@ -175,8 +175,10 @@ import {
formatPortfolioProfitPercent,
formatPortfolioProfitText,
resolveAssetsRialValue,
resolveBoursecuCopperUnit,
resolveMetalAssetsDisplayLabel,
} from '@/utils/productAssets'
import { formatWeightAmount } from '@/utils/usePrice'
import type { WalletMetalTotal } from '@/models/walletAssets'
import { useAssetsPortfolioChart } from './composables/useAssetsPortfolioChart'
import assetsCopperBanner from '@/assets/img/assets-copper-banner.webp'
@@ -265,10 +267,13 @@ const profitClass = computed(() => {
})
const summaryItems = computed(() => {
const items = metalTotals.value.map((metal) => ({
label: assetsMetalLabel(metal),
value: `${metal.total_weight} ${metal.unit}`,
}))
const items = metalTotals.value.map((metal) => {
const unit = metal.metal_id === 3 ? resolveBoursecuCopperUnit(metal.unit) : (metal.unit || 'کیلوگرم')
return {
label: assetsMetalLabel(metal),
value: `${formatWeightAmount(Number(metal.total_weight ?? 0))} ${unit}`,
}
})
items.push(
{ label: 'کیف پول ریالی', value: usePrice(cashWallet.value) },
{ label: 'ارزش ریالی مس', value: usePrice(metalRialValue.value) },
@@ -287,10 +292,11 @@ const assetRows = computed(() => {
const profit = portfolioInsight.value?.unrealized_profit_rial
const changeClass = profit == null || profit === 0 ? '' : profit > 0 ? 'is-profit' : 'is-loss'
const rows = metalTotals.value.flatMap((metal) => {
const unit = metal.metal_id === 3 ? resolveBoursecuCopperUnit(metal.unit) : (metal.unit || 'کیلوگرم')
const head = {
key: `metal-${metal.metal_id}`,
name: assetsMetalLabel(metal),
balance: `${metal.total_weight} ${metal.unit}`,
balance: `${formatWeightAmount(Number(metal.total_weight ?? 0))} ${unit}`,
rial: usePrice(metalRialValue.value),
change: pct || '—',
changeClass,
@@ -62,6 +62,7 @@ import {
formatPortfolioProfitPercent,
formatPortfolioProfitText,
resolveAssetsRialValue,
resolveBoursecuCopperUnit,
} from '@/utils/productAssets'
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
import { getTradeBuyPrice, lastPrice } from '@/utils/price'
@@ -76,7 +77,7 @@ const props = withDefaults(
const { assetsSummary } = useWalletAssets()
const portfolio = computed(() => assetsSummary.value?.portfolio)
const weightUnit = computed(() => assetsSummary.value?.meta?.weight_unit || 'گرم')
const weightUnit = computed(() => resolveBoursecuCopperUnit(assetsSummary.value?.meta?.weight_unit))
const totalRial = computed(() =>
Number(portfolio.value?.total_rial_value ?? assetsSummary.value?.total_rial_value ?? 0)
@@ -43,6 +43,7 @@ import {
formatPortfolioProfitLabel,
formatPortfolioProfitPercent,
formatPortfolioProfitText,
resolveBoursecuCopperUnit,
} from '@/utils/productAssets'
const props = withDefaults(
@@ -55,7 +56,7 @@ const props = withDefaults(
const { assetsSummary } = useWalletAssets()
const portfolio = computed(() => assetsSummary.value?.portfolio)
const weightUnit = computed(() => assetsSummary.value?.meta?.weight_unit || 'گرم')
const weightUnit = computed(() => resolveBoursecuCopperUnit(assetsSummary.value?.meta?.weight_unit))
const hasProfitInsight = computed(() => portfolio.value?.unrealized_profit_rial != null)
const profitTone = computed(() => formatPortfolioProfitLabel(portfolio.value?.unrealized_profit_rial))
+2 -11
View File
@@ -20,6 +20,7 @@ import { getBoursecuActionIconSrc } from '@/utils/config'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { getReferralShareText, getReferralShareTitle, shareReferralText } from '@/utils/referralShare'
import { normalizeAuthFormFields } from '@/utils/persianDigits'
import { performUserLogout } from '@/utils/authSession'
import { useSettingsStore } from '@/store/settingsStore'
import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature'
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
@@ -300,18 +301,8 @@ export function useUserPage() {
}
async function logout() {
userStore.userInfo = undefined
walletStore.walletInfo = undefined
clearWalletAssets()
resetHomePageData()
resetWalletPageData()
resetAddressPageData()
resetTransactionsPageData()
resetProductsPageData()
removeToken()
showLogoutConfirm.value = false
await router.push({ name: 'login' })
window.location.reload()
await performUserLogout()
}
onMounted(() => {
+2 -2
View File
@@ -68,11 +68,11 @@
<div class="text-black">مقدار {{ getMetalTypeTradeLabel() }} مبادله شده:</div>
<div :class="item.gold >= 0? 'text-green-600' : 'text-red-600'" class="flex text-xs items-center">
<div class="text-base ml-1 number-ltr">{{ formatGoldAmount(item.gold) }}</div>
گرم
{{ getWeightUnit() }}
</div>
</div>
<div v-if="item.order" class="flex justify-between mb-3">
<div class="text-black">قیمت هر گرم {{ getTradePriceUnitLabel() }} حین معامله:</div>
<div class="text-black">قیمت هر {{ getWeightUnit() }} {{ getTradePriceUnitLabel() }} حین معامله:</div>
<div class="flex text-xs items-center number-ltr">
<div class="text-base ml-1">{{ usePriceWithoutType(item.order?.price?? 0) }}</div>
ریال
+1 -1
View File
@@ -77,7 +77,7 @@
/>
</a-form-item>
<a-form-item
v-if="settingsStore.isReferralFieldVisible"
v-if="settingsStore.isReferralFieldVisible || disableForm"
:rules="referralIdRules"
:class="variant === 'desktop' ? '' : 'home_appear_fade_in_down'"
label="کد معرف"
@@ -56,7 +56,9 @@
درباره مس
</button>
<span class="dauth-form-links__sep" aria-hidden="true">|</span>
<span class="dauth-form-links__text">پشتیبانی</span>
<button type="button" class="dauth-form-links__link" @click="onSupportClick">
پشتیبانی
</button>
</nav>
<div class="dauth-page-footer__status">
<span class="dauth-page-footer__status-icon" aria-hidden="true">
@@ -100,7 +100,7 @@ function handleSuccessDismiss() {
</a-form-item>
<a-form-item
v-if="settingsStore.isReferralFieldVisible"
v-if="settingsStore.isReferralFieldVisible || disableForm"
:rules="referralIdRules"
label="کد معرف"
name="referral_id"
@@ -1191,18 +1191,19 @@
/* ─── موبایل (<768): لوگو + کارت + پشتیبانی، وسط‌چین عمودی ─── */
@media (max-width: 767px) {
.dauth.dauth--boursecu {
/* 100vh آیفون پشت نوار سافاری است؛ اسکرول داخل 100dvh تا دکمهٔ ثبت‌نام برسد */
height: 100dvh;
max-height: 100dvh;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* 100vh آیفون پشت نوار سافاری است؛ اسکرول روان در ارتفاع واقعی بدون قفل شدن */
height: auto !important;
min-height: 100dvh !important;
max-height: none !important;
overflow-x: hidden !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
touch-action: pan-y !important;
overscroll-behavior-y: auto !important;
display: flex;
flex-direction: column;
justify-content: flex-start;
justify-content: safe center;
padding: 12px 0 calc(24px + env(safe-area-inset-bottom, 0px));
padding: 16px 0 calc(36px + env(safe-area-inset-bottom, 0px));
.dauth-page-logo {
position: relative;
@@ -1367,14 +1368,18 @@
font-size: 11px;
}
/* شناور پایین روی دکمهٔ ثبت‌نام می‌نشیند؛ زیر کارت بیاید */
/* دکمه شناور پشتیبانی در موبایل: زیر کارت و بالاتر از لینک‌های فوتر نمایش داده شود و شناور روی فرم نیفتد */
.dauth-form-support--fab {
position: static !important;
display: inline-flex !important;
top: auto !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
width: auto !important;
align-self: center;
margin: 4px 0 0;
margin: 14px auto 0 !important;
z-index: 10 !important;
}
}
}
+17 -4
View File
@@ -60,6 +60,16 @@ onUnmounted(() => {
/* موبایل لاگین: تمام‌عرض — بدون فریم ۴۲۶ و حاشیه سفید دوطرف */
@media screen and (max-width: 767.98px) {
html.login-page-active {
html,
body {
overflow-x: hidden !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
height: auto !important;
min-height: 100dvh !important;
overscroll-behavior-y: auto !important;
}
body {
display: block !important;
justify-content: unset !important;
@@ -71,6 +81,9 @@ onUnmounted(() => {
width: 100% !important;
max-width: none !important;
background: #0b192e !important;
height: auto !important;
min-height: 100dvh !important;
overflow: visible !important;
}
.desktopConfig,
@@ -90,12 +103,12 @@ onUnmounted(() => {
.app-main,
.login-page-root,
.dauth.dauth--boursecu {
min-height: 0 !important;
height: 100dvh !important;
max-height: 100dvh !important;
min-height: 100dvh !important;
height: auto !important;
max-height: none !important;
overflow-x: hidden !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch;
-webkit-overflow-scrolling: touch !important;
background: #0b192e !important;
}