Make boursecu branch boursecu-only and simplify app loading.

Remove multi-tenant branches and default views, show logo-only boot and splash screens, and polish boursecu transactions tab styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 12:35:42 +02:00
co-authored by Cursor
parent 2affe61dd3
commit 59cceaf581
76 changed files with 211 additions and 2932 deletions
+35 -71
View File
@@ -17,8 +17,8 @@
<!---->
<style>
:root {
--boot-bg: #ffffff;
--boot-bg-soft: #f8fafc;
--boot-bg: #133050;
--boot-bg-soft: #133050;
--boot-card: rgba(255, 255, 255, 0.88);
--boot-border: rgba(15, 23, 42, 0.08);
--boot-text: #111827;
@@ -52,75 +52,26 @@
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
background:
radial-gradient(circle at top, var(--boot-orb), transparent 38%),
linear-gradient(
135deg,
var(--boot-bg) 0%,
var(--boot-bg-soft) 100%
);
background: var(--boot-bg);
color: var(--boot-text);
overflow: hidden;
}
.boot-loader__card {
position: relative;
width: min(340px, calc(100vw - 48px));
padding: 30px 22px 28px;
border-radius: 24px;
text-align: center;
background: var(--boot-card);
border: 1px solid var(--boot-border);
box-shadow: 0 18px 48px var(--boot-shadow);
backdrop-filter: blur(10px);
.boot-loader__logo {
width: min(72vw, 280px);
height: auto;
object-fit: contain;
animation: boot-logo-fade-in 0.45s ease-out both;
}
.boot-loader__orb {
position: absolute;
top: -72px;
left: 50%;
width: 150px;
height: 150px;
transform: translateX(-50%);
border-radius: 999px;
background: var(--boot-orb);
filter: blur(8px);
pointer-events: none;
}
.boot-loader__spinner {
position: relative;
z-index: 1;
width: 56px;
height: 56px;
margin: 0 auto 20px;
border-radius: 999px;
border: 4px solid var(--boot-spinner-track);
border-top-color: var(--boot-accent);
animation: boot-spin 0.9s linear infinite;
}
.boot-loader__title {
position: relative;
z-index: 1;
margin: 0;
font-size: 19px;
font-weight: 800;
letter-spacing: -0.02em;
}
.boot-loader__text {
position: relative;
z-index: 1;
margin: 10px 0 0;
color: var(--boot-muted);
font-size: 14px;
line-height: 2;
}
@keyframes boot-spin {
@keyframes boot-logo-fade-in {
from {
opacity: 0;
transform: scale(0.96);
}
to {
transform: rotate(360deg);
opacity: 1;
transform: scale(1);
}
}
@@ -323,6 +274,20 @@
setVar('--App-background-color', tenantBackground)
}
var logoEl = document.getElementById('boot-logo')
if (logoEl) {
if (user.name === 'boursecu') {
logoEl.src = '/boursecu-splash.png'
} else if (user.avatar) {
logoEl.src = user.avatar
}
}
if (user.name === 'boursecu') {
setVar('--boot-bg', '#133050')
setVar('--boot-bg-soft', '#133050')
}
document.title = user.name_fa || user.name || 'در حال بارگذاری...'
}
@@ -334,13 +299,12 @@
<body>
<div id="app">
<div class="boot-loader">
<div class="boot-loader__card">
<div class="boot-loader__orb"></div>
<div class="boot-loader__spinner"></div>
<h1 class="boot-loader__title">در حال هدایت به سامانه</h1>
<p class="boot-loader__text">لطفاً چند لحظه صبر کنید...</p>
</div>
<img
id="boot-logo"
class="boot-loader__logo"
src="/boursecu-splash.png"
alt=""
/>
</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+3 -51
View File
@@ -11,42 +11,13 @@
</div>
<AConfigProvider :theme="antTheme ?? undefined" direction="rtl">
<div v-if="showSplash" class="app-splash-screen flex justify-center" :style="{ backgroundColor: getProjectSplashBackground() }">
<template v-if="!isBoursecuProject()">
<div class="app-splash-screen__gradient app-splash-screen__gradient--top h-1/3 flex">
<img :src="img_topGradiant" alt="" class="absolute z-10 w-full h-full object-cover"/>
<img :src="img_pattern" alt="" class="absolute w-full h-full object-none"/>
</div>
<div class="app-splash-screen__gradient app-splash-screen__gradient--bottom h-1/3">
<img
:src="img_bottomGradiant"
alt=""
class="absolute z-10 w-full h-full object-cover"
/>
<img :src="img_pattern" alt="" class="absolute w-full h-full object-none"/>
</div>
</template>
<div class="self-center splash_appear_fade_in">
<img
:src="getProjectSplashLogo(config?.config?.avatar)"
:class="isBoursecuProject()
? 'w-[min(72vw,280px)] h-auto object-contain'
: hasCustomLoginLogoLayout()
? 'w-[80vw] max-w-[320px] h-auto object-contain'
: 'h-[20vh] object-contain'"
class="w-[min(72vw,280px)] h-auto object-contain"
alt=""
>
</div>
<div class="fixed w-full bottom-5 home_appear_fade_in_up">
<div
class="text-center text-xs"
:class="isBoursecuProject() ? 'text-white' : ''"
>
نسخه: {{ displayVersion }}
</div>
</div>
<div class="absolute bottom-10">
<a-spin :indicator="splashIndicator" size="small"/>
</div>
</div>
<div v-else class="app-main">
@@ -90,16 +61,10 @@
</template>
<script lang="ts" setup>
import pkg from '../package.json'
import { getDisplayAppVersion } from '@/config/config-cache'
import {computed, defineAsyncComponent, h, onMounted, ref, watch} from 'vue'
import img_topGradiant from '@/views/login/assets/img/gr_top_white.svg'
import img_pattern from '@/views/login/assets/img/pattern.svg'
import {LoadingOutlined} from '@ant-design/icons-vue'
import img_bottomGradiant from '@/views/login/assets/img/gr_bottom_white.svg'
import {computed, defineAsyncComponent, onMounted, ref, watch} from 'vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import {useConfigStore} from "@/store/configStore";
import {getProjectFontFamily, getProjectSplashBackground, getProjectSplashLogo, getProjectBrandRed, getProjectPrimaryColor, hasCustomLoginLogoLayout, isBoursecuProject, resetAppSurfaceBackground} from "@/utils/config";
import {getProjectFontFamily, getProjectSplashBackground, getProjectSplashLogo, getProjectBrandRed, getProjectPrimaryColor, resetAppSurfaceBackground} from "@/utils/config";
import {ConfigProvider as AConfigProvider} from 'ant-design-vue'
import BasicIcon from "@/components/BasicPacks/BasicIcon.vue";
import { useApplyDesktopLayoutClass, useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
@@ -126,24 +91,11 @@ const showDesktopFloatingCredit = computed(() => {
return route.name === 'login' || !!route.meta.hideMenu
})
const displayVersion = getDisplayAppVersion(pkg.version)
const config = useConfigStore()
const splashIndicator = computed(() => h(LoadingOutlined, {
style: {
fontSize: '24px',
color: isBoursecuProject() ? '#ffffff' : undefined,
},
spin: true
}))
const showSplash = ref(true)
const popVisible = ref(false)
// const GAROOSI_SUPPORT_URL = 'https://ble.ir/garoosigallery'
const supportLink = computed(() => {
// if (config.config?.name === 'garoosi') {
// return GAROOSI_SUPPORT_URL
// }
return buildSupportMessengerUrl(
supportMessengerSettings.value.messenger,
supportMessengerSettings.value.messengerId
-5
View File
@@ -5,7 +5,6 @@ import { useUserStore } from '@/store/userStore'
import { useFundUserStore } from '@/store/fundStore'
import type { NavigationGuardNext } from 'vue-router'
import { useAuthStore } from '@/store/useAuthStore'
import { getConfigApp } from '@/utils/config'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
const PUBLIC_ROUTE_NAMES = ['login', '404', 'educate', 'aboutUs'] as const
@@ -68,10 +67,6 @@ export function setupRouterGuards() {
clearTimeout(timeoutId)
}
if (to.name === 'giftCard' && getConfigApp('name') !== 'garoosi') {
return next({ name: (from.name as string) || 'home' })
}
const authenticated = await resolveSession()
if (!authenticated) {
+1 -11
View File
@@ -1,7 +1,6 @@
import {
getBoursecuActionIconSrc,
getBoursecuTransactionsIcon,
isBoursecuProject,
} from '@/utils/config'
import type { AppNavItem } from '../types'
@@ -43,21 +42,12 @@ const BOURSECU_PRIMARY_NAV: AppNavItem[] = [
},
]
const DEFAULT_PRIMARY_NAV: AppNavItem[] = [
{ id: 'home', routeName: 'home', label: 'صفحه اصلی', icon: 'vuesax-linear:home' },
{ id: 'transactions', routeName: 'transactions', label: 'تراکنش‌ها', icon: 'vuesax-linear:receipt-item' },
{ id: 'buyProduct', routeName: 'buyProduct', label: 'محصولات', icon: 'vuesax-linear:coin' },
{ id: 'wallet', routeName: 'wallet', label: 'کیف پول', icon: 'vuesax-linear:wallet-1' },
{ id: 'user', routeName: 'user', label: 'تنظیمات', icon: 'vuesax-linear:setting-2' },
]
export const SECONDARY_NAV: AppNavItem[] = [
{ id: 'notifyTrigger', routeName: 'notifyTrigger', label: 'هشدار قیمت', icon: 'vuesax-linear:bill' },
{ id: 'about', routeName: 'about', label: 'درباره ما', icon: 'vuesax-linear:info-circle' },
{ id: 'rules', routeName: 'rules', label: 'قوانین و مقررات', icon: 'vuesax-linear:task' },
{ id: 'giftCard', routeName: 'giftCard', label: 'ثبت کارت هدیه', icon: 'vuesax-linear:gift' },
]
export function getPrimaryNavItems(): AppNavItem[] {
return isBoursecuProject() ? BOURSECU_PRIMARY_NAV : DEFAULT_PRIMARY_NAV
return BOURSECU_PRIMARY_NAV
}
+2 -6
View File
@@ -1,8 +1,5 @@
<template>
<aside
class="desktop-sidebar"
:class="{ 'desktop-sidebar--boursecu': isBoursecu }"
>
<aside class="desktop-sidebar desktop-sidebar--boursecu">
<div class="desktop-sidebar__brand">
<img
:src="navLogo"
@@ -46,14 +43,13 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useConfigStore } from '@/store/configStore'
import { getProjectSplashLogo, isBoursecuProject } from '@/utils/config'
import { getProjectSplashLogo } from '@/utils/config'
import { getPrimaryNavItems, SECONDARY_NAV } from '../config/navigation'
import { useAppNavigation } from '../composables/useAppNavigation'
import DesktopNavItem from './DesktopNavItem.vue'
import AppCreditLink from '@/components/AppCreditLink.vue'
const config = useConfigStore()
const isBoursecu = isBoursecuProject()
const navLogo = computed(() => getProjectSplashLogo(config.config?.avatar))
const primaryItems = getPrimaryNavItems()
const secondaryItems = SECONDARY_NAV
+1 -8
View File
@@ -10,8 +10,6 @@
<MobileBottomNav
v-if="!hideMenu"
:is-boursecu="isBoursecu"
:nav-logo="navLogo"
:nav-item-class="navItemClass"
@navigate="navigateTo"
/>
@@ -19,18 +17,13 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, watch } from 'vue'
import { onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useConfigStore } from '@/store/configStore'
import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
import { useAppNavigation } from '../composables/useAppNavigation'
import { recoverMobileViewportAfterAuth, resetMobileAppScroll } from '../composables/useMobileScrollReset'
import MobileBottomNav from './MobileBottomNav.vue'
const config = useConfigStore()
const route = useRoute()
const isBoursecu = isBoursecuProject()
const navLogo = computed(() => getProjectMobileNavLogo(config.config?.avatar))
const {
hideMenu,
+5 -61
View File
@@ -2,7 +2,6 @@
<div class="bottom-appbar w-auto mx-auto bg-white">
<div class="tabs bottom-0">
<div
v-if="isBoursecu"
:class="navItemClass('buyProduct')"
class="tab"
@click="$emit('navigate', 'buyProduct')"
@@ -16,17 +15,6 @@
</div>
<div class="text-xs mt-1 font-bold">خرید و فروش</div>
</div>
<div
v-else
:class="navItemClass('home')"
class="tab"
@click="$emit('navigate', 'home')"
>
<div class="tab__icon">
<Icon icon="vuesax-linear:home" size="24" />
</div>
<div class="text-xs mt-1 font-bold">صفحه اصلی</div>
</div>
<div
:class="navItemClass('transactions')"
@@ -35,18 +23,15 @@
>
<div class="tab__icon">
<img
v-if="isBoursecu"
:src="boursecuTransactionsIcon"
alt=""
class="tab__icon-image"
/>
<Icon v-else icon="vuesax-linear:receipt-item" size="24" />
</div>
<div class="text-xs mt-1 font-bold">تراکنش ها</div>
</div>
<div
v-if="isBoursecu"
:class="navItemClass('home')"
class="tab"
@click="$emit('navigate', 'home')"
@@ -60,28 +45,6 @@
</div>
<div class="text-xs mt-1 font-bold">صفحه اصلی</div>
</div>
<div v-else class="main_in" @click="$emit('navigate', 'buyProduct')">
<div
class="circle_in"
:class="isWhiteNavLogo ? 'is-white-nav-logo' : 'text-white'"
>
<div class="circle_into">
<div
v-if="projectName === 'garoosi'"
class="p-2 text-tertiary flex flex-col justify-center items-center"
>
<Icon icon="vuesax-linear:coin" />
<div class="text-[9px] font-bold">محصولات</div>
</div>
<img
v-else
class="p-2"
:src="navLogo"
alt=""
/>
</div>
</div>
</div>
<div
:class="navItemClass('wallet')"
@@ -90,47 +53,36 @@
>
<div class="tab__icon">
<img
v-if="isBoursecu"
:src="boursecuWalletIcon"
alt=""
class="tab__icon-image"
/>
<Icon v-else icon="vuesax-linear:wallet-1" size="24" />
</div>
<div class="text-xs mt-1 font-bold">کیف پول</div>
</div>
<div
:class="navItemClass(userNavItem.routeName)"
:class="navItemClass('user')"
class="tab"
@click="$emit('navigate', userNavItem.routeName)"
@click="$emit('navigate', 'user')"
>
<div class="tab__icon">
<img
v-if="isBoursecu && userNavItem.routeName === 'user'"
:src="boursecuSettingsIcon"
alt=""
class="tab__icon-image"
/>
<Icon v-else :icon="userNavItem.icon" size="24" />
</div>
<div class="text-xs mt-1 font-bold">{{ userNavItem.label }}</div>
<div class="text-xs mt-1 font-bold">تنظیمات</div>
</div>
</div>
<div v-if="!isBoursecu" class="main">
<div class="circle" />
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { getBoursecuActionIconSrc, getConfigApp } from '@/utils/config'
import { getBoursecuActionIconSrc } from '@/utils/config'
defineProps<{
isBoursecu: boolean
navLogo: string
navItemClass: (routeName: string) => string
}>()
@@ -138,19 +90,11 @@ defineEmits<{
navigate: [routeName: string]
}>()
const projectName = getConfigApp('name')
const userNavItem = computed(() =>
projectName === 'abshodehrahimi'
? { routeName: 'notifyTrigger', label: 'هشدار قیمت', icon: 'vuesax-linear:bill' }
: { routeName: 'user', label: 'تنظیمات', icon: 'vuesax-linear:setting-2' },
)
const boursecuBuySellIcon = getBoursecuActionIconSrc('buySell') ?? ''
const boursecuTransactionsIcon = getBoursecuActionIconSrc('transactions') ?? ''
const boursecuWalletIcon = getBoursecuActionIconSrc('wallet') ?? ''
const boursecuHomeIcon = getBoursecuActionIconSrc('home') ?? ''
const boursecuSettingsIcon = getBoursecuActionIconSrc('settings') ?? ''
const whiteNavLogoProjects = ['goldenramin']
const isWhiteNavLogo = computed(() => whiteNavLogoProjects.includes(projectName))
</script>
<style scoped>
-5
View File
@@ -32,13 +32,8 @@ const KATOD_DEVIATION_FACTOR = 200
/** نمودار boursecu وقتی metal_type = کاتد مس */
export function shouldAmplifyKatodMesChart(options: {
isBoursecu: boolean
metalTypeSetting?: string | number | null
}): boolean {
if (!options.isBoursecu) {
return false
}
const metal = String(options.metalTypeSetting ?? '').trim()
return metal === 'کاتد مس'
}
+30 -123
View File
@@ -9,14 +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 bazretalaLoginLogo from '@/assets/img/bazretala-login-logo.png'
import bazretalaNavLogo from '@/assets/img/bazretala-nav-logo.png'
import bazretalaFundBg from '@/assets/img/bazretala-fund-bg.png'
import bazretalaEmptyFund from '@/assets/img/bazretala-empty-fund.png'
import soroushgoldLoginLogo from '@/assets/img/soroushgold-login-logo.png'
export function getConfigApp(key: string) {
const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user:{}
const config = window.__APP_CONFIG__ ? window.__APP_CONFIG__.user : {}
if (config) {
return config[key]
@@ -24,32 +19,15 @@ export function getConfigApp(key: string) {
return null
}
/** boursecu-only branch */
export function isBoursecuProject() {
return getConfigApp('name') === 'boursecu'
return true
}
export function isBazretalaProject() {
return getConfigApp('name') === 'bazretala'
}
export function isSoroushgoldProject() {
return getConfigApp('name') === 'soroushgold'
}
export function isNiazitalaProject() {
return getConfigApp('name') === 'niazitala'
}
const DEFAULT_WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL = [10_000_000, 50_000_000, 100_000_000] as const
/** niazitala: 5 / 10 / 15 میلیون تومان — ذخیره و نمایش به ریال */
const NIAZITALA_WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL = [50_000_000, 100_000_000, 150_000_000] as const
const WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL = [10_000_000, 50_000_000, 100_000_000] as const
export function getWalletDepositQuickAmountsRial(): readonly number[] {
if (isNiazitalaProject()) {
return NIAZITALA_WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL
}
return DEFAULT_WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL
return WALLET_DEPOSIT_QUICK_AMOUNTS_RIAL
}
export function getBoursecuTransactionsIcon() {
@@ -100,7 +78,7 @@ export function getBoursecuActionIconSrc(actionId: string): string | undefined {
}
export function hasCustomLoginLogoLayout() {
return isBoursecuProject() || isBazretalaProject()
return true
}
export const BOURSECU_BRAND_RED = '#bc3c3b'
@@ -111,22 +89,21 @@ export const BOURSECU_COPPER_BG_LIGHT = '#fdf0e6'
export const BOURSECU_COPPER_BG_GRADIENT = 'linear-gradient(135deg, #c98a4a 0%, #a86528 100%)'
export function getProjectBrandRed() {
return isBoursecuProject() ? BOURSECU_BRAND_RED : null
return BOURSECU_BRAND_RED
}
/** رنگ accent تم از تنظیمات tenant — بدون override پروژه‌ای */
/** رنگ accent تم از تنظیمات tenant */
export function getProjectPrimaryColor() {
const color = getConfigApp('color')
return color != null ? String(color) : undefined
}
/** فقط رنگ خط/ناحیه نمودار قیمت در بورس مس */
export function getBoursecuChartColor() {
return BOURSECU_BRAND_BLUE
}
export function resolveProjectColor(color?: string | null) {
if (!color || !isBoursecuProject()) {
if (!color) {
return color
}
@@ -137,131 +114,66 @@ export function resolveProjectColor(color?: string | null) {
return color
}
/** لوگوی پنل برند لاگین دسکتاپ — فقط soroushgold لوگوی اختصاصی دارد */
export function getProjectDesktopLoginBrandLogo(avatar?: string | null) {
if (isSoroushgoldProject()) {
return soroushgoldLoginLogo
}
return getProjectSplashLogo(avatar)
export function getProjectDesktopLoginBrandLogo(_avatar?: string | null) {
return getProjectSplashLogo()
}
export function getProjectLoginLogo(avatar?: string | null) {
if (isBoursecuProject()) {
return boursecuLoginLogo
}
if (isBazretalaProject()) {
return bazretalaLoginLogo
}
return avatar ?? getConfigApp('avatar')
export function getProjectLoginLogo(_avatar?: string | null) {
return boursecuLoginLogo
}
/** لوگوی صفحه لودینگ اولیه — جدا از لوگوی لاگین/ناوبری */
export function getProjectSplashLogo(avatar?: string | null) {
if (isBoursecuProject()) {
return boursecuSplashLogo
}
return getProjectLoginLogo(avatar)
export function getProjectSplashLogo(_avatar?: string | null) {
return boursecuSplashLogo
}
export function getProjectNavLogo(avatar?: string | null) {
if (isBazretalaProject()) {
return bazretalaNavLogo
}
if (isBoursecuProject()) {
return boursecuLogo
}
return avatar ?? getConfigApp('avatar')
export function getProjectNavLogo(_avatar?: string | null) {
return boursecuLogo
}
/** لوگوی ناوبری موبایل — boursecu همان لوگوی لاگین (پس‌زمینه سفید) */
export function getProjectMobileNavLogo(avatar?: string | null) {
if (isBoursecuProject()) {
return boursecuLoginLogo
}
if (isSoroushgoldProject()) {
return soroushgoldLoginLogo
}
return getProjectNavLogo(avatar)
export function getProjectMobileNavLogo(_avatar?: string | null) {
return boursecuLoginLogo
}
export const BAZRETALA_SUPPORT_PHONE = '09121784258'
export function getProjectSupportPhone() {
if (isBazretalaProject()) {
return BAZRETALA_SUPPORT_PHONE
}
return ''
}
export function getProjectSupportPhoneLink() {
const phone = getProjectSupportPhone()
return phone ? `tel:${phone}` : 'tel:'
return 'tel:'
}
export const BAZRETALA_BALE_ID = 'bazretala'
export function getProjectBaleLink() {
if (isBazretalaProject()) {
return `https://ble.ir/${BAZRETALA_BALE_ID}`
}
return 'https://ble.ir/'
}
export function getProjectBaleContactLink() {
if (getConfigApp('name') === 'garoosi') {
return 'https://ble.ir/garoosiadmin'
}
return getProjectBaleLink()
}
/** لینک دکمه «تماس با پشتیبانی» — bazretala به بله، garoosi تماس، بقیه تلفن پشتیبانی */
export function getProjectSupportContactLink() {
if (isBazretalaProject()) {
return getProjectBaleLink()
}
if (getConfigApp('name') === 'garoosi') {
return 'tel:09031265925'
}
return getProjectSupportPhoneLink()
}
export function getProjectFundHeaderBackground(defaultBackground: string) {
if (isBazretalaProject()) {
return bazretalaFundBg
}
return defaultBackground
}
export function getProjectFundEmptyImage(defaultImage: string) {
if (isBazretalaProject()) {
return bazretalaEmptyFund
}
return defaultImage
}
export function getProjectFundEmptyText() {
if (isBazretalaProject()) {
return 'یک بذر جدید بکار!'
}
return 'یک گنج جدید بساز!'
}
export function getProjectLoginTaglineLines() {
if (!isBoursecuProject()) {
return null
}
return [
'مرجع هوشمند معاملات',
' و سرمایه‌گذاری در صنعت مس',
]
return ['مرجع هوشمند معاملات', ' و سرمایه‌گذاری در صنعت مس']
}
export function getProjectSplashBackground() {
return isBoursecuProject() ? BOURSECU_BRAND_NAV : '#ffffff'
return BOURSECU_BRAND_NAV
}
/** Reset splash-time inline backgrounds so mobile overscroll shows white again. */
export function resetAppSurfaceBackground() {
const background = '#ffffff'
document.documentElement.style.backgroundColor = background
@@ -274,30 +186,25 @@ export function resetAppSurfaceBackground() {
}
export function getProjectFontFamily() {
return getConfigApp('name') === 'boursecu' ? "'dana', sans-serif" : "'iran-sans', sans-serif"
return "'dana', sans-serif"
}
export function applyProjectFont() {
document.documentElement.style.setProperty('--App-font-family', getProjectFontFamily())
if (getConfigApp('name') === 'boursecu') {
document.documentElement.classList.add('font-dana')
}
document.documentElement.classList.add('font-dana')
}
export function isTWA() {
if (sessionStorage.getItem('appMode') === 'twa') return true;
if (sessionStorage.getItem('appMode') === 'twa') return true
if (document.referrer.startsWith('android-app://')) {
sessionStorage.setItem('appMode', 'twa');
return true;
sessionStorage.setItem('appMode', 'twa')
return true
}
return false;
return false
}
export function applyProjectTheme() {
applyProjectFont()
if (!isBoursecuProject()) {
return
}
document.documentElement.classList.add('project-boursecu')
document.documentElement.style.setProperty('--App-brand-nav', BOURSECU_BRAND_NAV)
document.documentElement.style.setProperty('--App-brand-red', BOURSECU_BRAND_RED)
+2 -9
View File
@@ -76,14 +76,7 @@ export function resolveBoursecuAssetMetalTotals(
return ordered
}
export function resolveMetalAssetsDisplayLabel(
metal: WalletMetalTotal,
isBoursecuLayout: boolean
): string {
if (!isBoursecuLayout) {
return metal.label
}
export function resolveMetalAssetsDisplayLabel(metal: WalletMetalTotal): string {
switch (metal.metal_id) {
case METAL_COPPER:
return 'دارایی مس'
@@ -121,7 +114,7 @@ export function formatBoursecuMetalModalText(metal: WalletMetalTotal): string {
case METAL_SILVER:
return 'دارایی نقره = موجودی نقره'
default:
return `${resolveMetalAssetsDisplayLabel(metal, true)} = موجودی ${metal.label}`
return `${resolveMetalAssetsDisplayLabel(metal)} = موجودی ${metal.label}`
}
}
+6 -25
View File
@@ -1,7 +1,6 @@
import type { SelectItem } from '@/models/SelectItem'
import type { Product } from '@/models/products'
import type { WalletInventoryCard } from '@/models/walletAssets'
import { isBoursecuProject } from '@/utils/config'
import { useSettingsStore } from '@/store/settingsStore'
import {
isLegacyMaftolGroup,
@@ -25,10 +24,6 @@ export const BOURSECU_GROUP_MAFTOL = LEGACY_GROUP_MAFTOL
export const BOURSECU_WALLET_LABEL_GENERIC = 'موجودی کیف'
export function isBoursecuDealProductGroup(groupType?: string | null) {
if (!isBoursecuProject()) {
return false
}
const group = normalizeProductGroupType(groupType)
return group === BOURSECU_GROUP_MAFTOL
|| group === BOURSECU_GROUP_KATOD
@@ -38,7 +33,7 @@ export function isBoursecuDealProductGroup(groupType?: string | null) {
export function isMaftolMesGroup(groupType?: string | null) {
const group = normalizeProductGroupType(groupType)
return group === BOURSECU_GROUP_MAFTOL || (isBoursecuProject() && group === 'مس')
return group === BOURSECU_GROUP_MAFTOL || group === 'مس'
}
export function isKatodMesGroup(groupType?: string | null) {
@@ -46,10 +41,6 @@ export function isKatodMesGroup(groupType?: string | null) {
}
export function isShamshMesGroup(groupType?: string | null, title?: string | null) {
if (!isBoursecuProject()) {
return false
}
const group = normalizeProductGroupType(groupType)
if (group === 'شمش') {
return true
@@ -115,11 +106,9 @@ export function getProductWeightUnit(product?: ProductUnitSource | null) {
return unit
}
if (isBoursecuProject()) {
const group = normalizeProductGroupType(product?.group_type)
if (group === BOURSECU_GROUP_MAFTOL || group === BOURSECU_GROUP_KATOD || group === BOURSECU_GROUP_ABSHODE || group === 'مس' || isMeltedTradeGroup(group)) {
return getWeightUnit()
}
const group = normalizeProductGroupType(product?.group_type)
if (group === BOURSECU_GROUP_MAFTOL || group === BOURSECU_GROUP_KATOD || group === BOURSECU_GROUP_ABSHODE || group === 'مس' || isMeltedTradeGroup(group)) {
return getWeightUnit()
}
if (!isCountBasedProduct(product) || isMeltedTradeGroup(product?.group_type)) {
@@ -209,7 +198,7 @@ export function isCountBasedProduct(product?: ProductUnitSource | null) {
}
const group = normalizeProductGroupType(product.group_type)
if (group === BOURSECU_GROUP_MAFTOL || isLegacyMaftolGroup(group) || (isBoursecuProject() && group === 'مس')) {
if (group === BOURSECU_GROUP_MAFTOL || isLegacyMaftolGroup(group) || group === 'مس') {
return true
}
@@ -407,10 +396,6 @@ export function getProductDealMethodOptions(
): SelectItem[] {
const settingsStore = useSettingsStore()
if (!isBoursecuProject()) {
return getDefaultTypeDealOptions(context, isBuy)
}
if (isMaftolMesGroup(groupType)) {
if (isBuy) {
return [
@@ -518,10 +503,6 @@ export function getBoursecuMetalWalletLabel(groupType?: string | null) {
}
export function getGoldWalletLabel(groupType?: string | null) {
if (!isBoursecuProject()) {
return getMainMetalWalletLabel()
}
const group = normalizeProductGroupType(groupType)
if (group === BOURSECU_GROUP_MAFTOL
|| group === BOURSECU_GROUP_KATOD
@@ -575,7 +556,7 @@ export function formatCashWalletBalanceText(cashFormatted: string | number, grou
/** برچسب کیف فلزی متناسب با نحوه پرداخت (مثلاً کاتد مس برای خرید مفتول با کیف کاتد) */
export function getMetalWalletLabelForDealMethod(method?: string | null, groupType?: string | null) {
if (!isBoursecuProject() || method === 'katod') {
if (method === 'katod') {
return getMainMetalWalletLabel()
}
@@ -2,10 +2,8 @@
<div
:class="[
variant === 'desktop'
? (isBoursecu ? 'dabout-content--boursecu' : 'dabout-content--default')
: (isBoursecuProject()
? 'border-solid border-2 border-primary text-primary rounded-[12px] p-4 text-xs leading-5 mobile-content-box'
: 'bg-primary text-white rounded-[12px] p-4 text-xs leading-5 mobile-content-box'),
? 'dabout-content--boursecu'
: 'border-solid border-2 border-primary text-primary rounded-[12px] p-4 text-xs leading-5 mobile-content-box',
'dabout-content',
]"
>
@@ -63,9 +61,5 @@ withDefaults(
{ variant: 'mobile' },
)
const {
aboutSections,
isBoursecu,
isBoursecuProject,
} = useAboutPageContext()
const { aboutSections } = useAboutPageContext()
</script>
@@ -3,15 +3,12 @@ import { useConfigStore } from '@/store/configStore'
import {
getProjectBaleLink,
getProjectSupportPhoneLink,
isBoursecuProject,
} from '@/utils/config'
import { getAboutSections, type AboutSection } from '../aboutContent'
export interface AboutPageContext {
config: ReturnType<typeof useConfigStore>
aboutSections: ComputedRef<AboutSection[]>
isBoursecu: ComputedRef<boolean>
isBoursecuProject: typeof isBoursecuProject
getProjectSupportPhoneLink: typeof getProjectSupportPhoneLink
getProjectBaleLink: typeof getProjectBaleLink
}
@@ -22,15 +19,11 @@ export function useAboutPage() {
const config = useConfigStore()
const nameProject = config.config.name_fa
const isBoursecu = computed(() => isBoursecuProject())
const aboutSections = computed(() => getAboutSections(nameProject, isBoursecu.value))
const aboutSections = computed(() => getAboutSections(nameProject, true))
const context: AboutPageContext = {
config,
aboutSections,
isBoursecu,
isBoursecuProject,
getProjectSupportPhoneLink,
getProjectBaleLink,
}
@@ -5,7 +5,6 @@ import { createAddress, getCity, updateAddress } from '@/api/address'
import type { Address } from '@/models/address'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { loading, loadingMore } from '@/utils'
import { isBazretalaProject } from '@/utils/config'
import {
addressItems,
addressProvinceList,
@@ -23,7 +22,6 @@ export interface AddressPageContext {
formRef: Ref<FormInstance | undefined>
loading: typeof loading
loadingMore: typeof loadingMore
isBazretalaProject: typeof isBazretalaProject
resetFields: () => void
openAdd: () => void
openEdit: (val: Address) => void
@@ -120,7 +118,6 @@ export function useAddressPage() {
formRef,
loading,
loadingMore,
isBazretalaProject,
resetFields,
openAdd,
openEdit,
@@ -7,13 +7,6 @@
<p class="daddr-topbar__subtitle">آدرس‌های تحویل سفارش خود را مدیریت کنید</p>
</header>
<div
v-if="isBazretalaProject()"
class="daddr-notice"
>
جهت تحویل فیزیکی محصول در تهران و قم ازطریق معرف اقدام فرمائید یا با شماره امورمالی و حسابداری 09925202099 درارتباط باشید.
</div>
<div class="daddr-grid">
<section class="daddr-surface daddr-form">
<div class="daddr-form__header">
@@ -78,7 +71,6 @@ const {
items,
loading,
editId,
isBazretalaProject,
openAdd,
openEdit,
initItems,
@@ -9,13 +9,6 @@
:class="loading ? 'h-screen' : ''"
class="relative z-1 px-3 mpage-content pb-2 overflow-y-scroll mpage-scroll"
>
<div
v-if="isBazretalaProject()"
class="text-justify text-sm text-primary mb-4 px-1 leading-7"
>
جهت تحویل فیزیکی محصول در تهران و قم ازطریق معرف اقدام فرمائید یا با شماره امورمالی و حسابداری 09925202099 درارتباط باشید.
</div>
<div
v-if="!loading"
class="pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]"
@@ -58,7 +51,6 @@ const {
items,
loading,
addAddressVisible,
isBazretalaProject,
openAdd,
openEdit,
initItems,
@@ -36,8 +36,7 @@
<script lang="ts" setup>
import { computed } from 'vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import { getProjectBaleContactLink, getProjectSupportContactLink, isBazretalaProject } from '@/utils/config'
import { useEducatePageContext } from '../composables/useEducatePage'
import { getProjectBaleContactLink, getProjectSupportContactLink } from '@/utils/config'
withDefaults(
defineProps<{
@@ -46,11 +45,7 @@ withDefaults(
{ variant: 'mobile' },
)
const { config } = useEducatePageContext()
const showBaleSupport = computed(
() => config.config.name === 'garoosi' || isBazretalaProject(),
)
const showBaleSupport = computed(() => false)
const supportHref = computed(() => getProjectSupportContactLink())
const baleHref = computed(() => getProjectBaleContactLink())
const opensSupportInNewTab = computed(() => supportHref.value.startsWith('http'))
@@ -6,13 +6,11 @@ import { useConfigStore } from '@/store/configStore'
import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets'
import { resolveSummaryMetalWeight, fundPageInventoryCards } from '@/utils/productAssets'
import { useWalletStore } from '@/store/walletStore'
import { getLastPrices } from '@/utils/price'
import {
getProjectFundEmptyImage,
getProjectFundEmptyText,
getProjectFundHeaderBackground,
isBazretalaProject,
} from '@/utils/config'
import img from '@/assets/img/user_ly1.svg'
import empty from '@/assets/img/cash_empty.svg'
@@ -30,7 +28,6 @@ export interface FundPageContext {
fundHeaderBackground: string
fundEmptyImage: string
fundEmptyText: string
isBazretalaProject: typeof isBazretalaProject
formatMetalTotal: (metal: WalletMetalTotal) => string
openFundInfo: (productId: number) => void
goBack: () => void
@@ -41,7 +38,6 @@ export const FUND_PAGE_KEY: InjectionKey<FundPageContext> = Symbol('fundPage')
export function useFundPage(isDesktop: Ref<boolean>) {
const nameProject = useConfigStore().config.name
const { assetsSummary, loadWalletAssets } = useWalletAssets()
const walletStore = useWalletStore()
const fundHeaderBackground = getProjectFundHeaderBackground(img)
const fundEmptyImage = getProjectFundEmptyImage(empty)
const fundEmptyText = getProjectFundEmptyText()
@@ -77,9 +73,6 @@ export function useFundPage(isDesktop: Ref<boolean>) {
try {
loading.value = true
const tasks = [loadWalletAssets(true), getLastPrices()]
if (isBazretalaProject()) {
tasks.push(walletStore.getMyWallet())
}
await Promise.all(tasks)
} catch (e) {
console.log(e)
@@ -101,7 +94,6 @@ export function useFundPage(isDesktop: Ref<boolean>) {
fundHeaderBackground,
fundEmptyImage,
fundEmptyText,
isBazretalaProject,
formatMetalTotal,
openFundInfo,
goBack,
@@ -13,15 +13,11 @@
</div>
</header>
<section
class="dfund-summary"
:class="{ 'dfund-summary--bazretala': isBazretalaProject() }"
>
<section class="dfund-summary">
<img
:src="fundHeaderBackground"
alt=""
class="dfund-summary__bg"
:class="{ 'dfund-summary__bg--cover': isBazretalaProject() }"
/>
<div class="dfund-summary__content">
<h2 class="dfund-summary__title">موجودی دارایی‌ها</h2>
@@ -31,17 +27,7 @@
<strong>{{ countBasedInventory }} عدد</strong>
</div>
<template v-if="isBazretalaProject()">
<div class="dfund-summary__row">
<span>دارایی نقره</span>
<strong>{{ walletStore.walletInfo?.count_silver ?? 0 }} گرم</strong>
</div>
<div class="dfund-summary__row">
<span>دارایی طلایی</span>
<strong>{{ walletStore.walletInfo?.count_gold_wallet ?? 0 }} گرم</strong>
</div>
</template>
<template v-else-if="meta?.single_metal && primaryMetalTotal">
<template v-if="meta?.single_metal && primaryMetalTotal">
<div class="dfund-summary__row">
<span>{{ primaryMetalTotal.label }}</span>
<strong>{{ formatMetalTotal(primaryMetalTotal) }}</strong>
@@ -59,7 +45,7 @@
</template>
<div
v-if="meta?.has_count_based_products && !isBazretalaProject()"
v-if="meta?.has_count_based_products"
class="dfund-summary__row"
>
<span>مجموع موجودی</span>
@@ -112,12 +98,9 @@
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { loading } from '@/utils'
import { usePriceWithoutType } from '@/utils/usePrice'
import { useWalletStore } from '@/store/walletStore'
import Item from '../components/Item.vue'
import { useFundPageContext } from '../composables/useFundPage'
const walletStore = useWalletStore()
const {
nameProject,
meta,
@@ -130,7 +113,6 @@ const {
fundHeaderBackground,
fundEmptyImage,
fundEmptyText,
isBazretalaProject,
formatMetalTotal,
openFundInfo,
goBack,
+6 -47
View File
@@ -9,22 +9,14 @@
<div class="mr-2">دارایی ها</div>
</div>
<div
class="w-screen md:w-full h-[70vh] fixed md:absolute overflow-hidden"
:class="isBazretalaProject() ? 'bazretala-fund-header' : 'text-tertiary bg-primary'"
class="w-screen md:w-full h-[70vh] fixed md:absolute overflow-hidden text-tertiary bg-primary"
>
<img
:src="fundHeaderBackground"
alt=""
:class="
isBazretalaProject()
? 'pointer-events-none z-0 absolute inset-0 w-full object-cover object-center'
: 'pointer-events-none z-0 w-full absolute bottom-0 left-0 opacity-5'
"
class="pointer-events-none z-0 w-full absolute bottom-0 left-0 opacity-5"
/>
<div
class="mobile-page-body--pt15 flex flex-col relative z-10"
:class="isBazretalaProject() ? 'text-white' : 'text-tertiary'"
>
<div class="mobile-page-body--pt15 flex flex-col relative z-10 text-tertiary">
<div class="font-bold mr-5 z-20">موجودی دارایی ها</div>
<div
v-if="meta?.has_count_based_products"
@@ -33,21 +25,7 @@
محصولات:
<div class="text-l mr-4 font-bold">{{ countBasedInventory }} عدد</div>
</div>
<template v-if="isBazretalaProject()">
<div class="flex justify-center mt-2 w-full z-20">
دارایی نقره:
<div class="text-l mr-4 font-bold">
{{ walletStore.walletInfo?.count_silver ?? 0 }}گرم
</div>
</div>
<div class="flex justify-center mt-2 w-full z-20">
دارایی طلایی:
<div class="text-l mr-4 font-bold">
{{ walletStore.walletInfo?.count_gold_wallet ?? 0 }}گرم
</div>
</div>
</template>
<template v-else-if="meta?.single_metal && primaryMetalTotal">
<template v-if="meta?.single_metal && primaryMetalTotal">
<div class="flex justify-center mt-2 w-full z-20">
{{ primaryMetalTotal.label }}:
<div class="text-l mr-4 font-bold">{{ formatMetalTotal(primaryMetalTotal) }}</div>
@@ -64,7 +42,7 @@
</div>
</template>
<div
v-if="meta?.has_count_based_products && !isBazretalaProject()"
v-if="meta?.has_count_based_products"
class="flex justify-center mt-2 w-full z-20"
>
مجموع موجودی:
@@ -93,7 +71,7 @@
<img
:src="fundEmptyImage"
alt=""
:class="isBazretalaProject() ? 'w-48 max-w-[11rem]' : 'w-70'"
class="w-70"
/>
<div v-if="nameProject !== 'abaeigold'" class="text-center text-lg">
{{ fundEmptyText }}
@@ -120,12 +98,9 @@
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { loading } from '@/utils'
import { usePriceWithoutType } from '@/utils/usePrice'
import { useWalletStore } from '@/store/walletStore'
import Item from '../components/Item.vue'
import { useFundPageContext } from '../composables/useFundPage'
const walletStore = useWalletStore()
const {
nameProject,
meta,
@@ -138,24 +113,8 @@ const {
fundHeaderBackground,
fundEmptyImage,
fundEmptyText,
isBazretalaProject,
formatMetalTotal,
openFundInfo,
goBack,
} = useFundPageContext()
</script>
<style scoped>
.bazretala-fund-header {
background-color: #000;
}
.bazretala-fund-header::after {
content: '';
position: absolute;
inset: 0;
z-index: 1;
background: rgba(0, 0, 0, 0.45);
pointer-events: none;
}
</style>
+2 -13
View File
@@ -5,21 +5,12 @@
:class="{ 'app-header--scrolled': isHeaderScrolled }"
>
<div class="flex justify-between my-2 items-center">
<div v-if="isBoursecuProject()" class="flex items-center gap-2">
<div class="flex items-center gap-2">
<img :src="projectLogo" alt="" class="h-10 w-auto max-w-[9rem] object-contain" />
</div>
<div v-else class="flex items-center gap-2" @click.stop="router.push({ name: 'user' })">
<div class="z-100000 rounded-full border-1 text-primary border-primary">
<Icon icon="vuesax-linear:user" size="20" />
</div>
<div class="text-primary color-transition font-bold">
{{ userStore.userInfo?.name }}
</div>
</div>
<div class="flex items-center gap-2">
<button
v-if="isBoursecuProject()"
type="button"
class="mhome-header-btn"
@click="router.push({ name: 'user' })"
@@ -42,16 +33,14 @@
<script setup lang="ts">
import { computed, nextTick, onActivated, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/store/userStore'
import router from '@/router'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { getProjectMobileNavLogo, isBoursecuProject } from '@/utils/config'
import { getProjectMobileNavLogo } from '@/utils/config'
const headerRef = ref<HTMLElement | null>(null)
const isHeaderScrolled = ref(false)
const route = useRoute()
const userStore = useUserStore()
const projectLogo = computed(() => getProjectMobileNavLogo())
let intersectionObserver: IntersectionObserver | null = null
@@ -1,127 +1,18 @@
<template>
<div :class="rootClass">
<div
v-if="!boursecuLayout && meta?.has_count_based_products"
:class="rowClass"
class="flex justify-center w-full z-20 mb-2"
>
محصولات:
<div class="text-l mr-2 font-bold">{{ usePriceWithoutType(countBasedInventory, false) }} عدد</div>
</div>
<div class="assets-section assets-section--boursecu rounded-lg px-3 py-3 mt-2">
<BoursecuAssetsSummary
v-if="boursecuLayout"
variant="mobile"
icon-color="primary"
/>
<template v-else-if="meta?.single_metal && primaryMetalTotal">
<div :class="[rowClass, meta?.has_count_based_products ? 'mt-2' : '']" class="flex justify-center w-full z-20">
{{ primaryMetalTotal.label }}:
<div class="text-l mr-2 font-bold">{{ formatMetalTotal(primaryMetalTotal) }}</div>
<BasicIcon
icon="vuesax-linear:info-circle"
size="18"
:color="iconColor"
@click="$emit('openGoldModal', primaryMetalTotal)"
/>
</div>
<PortfolioProfitInsight variant="mobile" />
<div :class="[rowClass, 'mt-2']" class="flex justify-center w-full z-20">
دارایی ریالی:
<div class="text-l mr-2 font-bold">{{ usePriceWithoutType(cashWallet) }} ریال</div>
<BasicIcon
icon="vuesax-linear:info-circle"
size="18"
:color="iconColor"
@click="$emit('openRialModal')"
/>
</div>
</template>
<template v-else-if="!boursecuLayout">
<div
v-for="(metal, index) in metalTotals"
:key="metal.metal_id"
:class="[rowClass, index > 0 || meta?.has_count_based_products ? 'mt-2' : '']"
class="flex justify-center w-full z-20"
>
{{ metal.label }}:
<div class="text-l mr-2 font-bold">{{ formatMetalTotal(metal) }}</div>
<BasicIcon
v-if="metal.metal_id === metalTotals[0]?.metal_id"
icon="vuesax-linear:info-circle"
size="18"
:color="iconColor"
@click="$emit('openGoldModal', metal)"
/>
</div>
<PortfolioProfitInsight variant="mobile" />
<div :class="[rowClass, 'mt-2']" class="flex justify-center w-full z-20">
دارایی ریالی:
<div class="text-l mr-2 font-bold">{{ usePriceWithoutType(cashWallet) }} ریال</div>
<BasicIcon
icon="vuesax-linear:info-circle"
size="18"
:color="iconColor"
@click="$emit('openRialModal')"
/>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import BoursecuAssetsSummary from './BoursecuAssetsSummary.vue'
import PortfolioProfitInsight from './PortfolioProfitInsight.vue'
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets'
import { resolveSummaryMetalWeight } from '@/utils/productAssets'
const props = withDefaults(
defineProps<{
plain?: boolean
boursecuLayout?: boolean
}>(),
{ plain: false, boursecuLayout: false }
)
defineEmits<{
openGoldModal: [metal: WalletMetalTotal, modalText?: string, modalTitle?: string, modalContent?: string]
openRialModal: []
}>()
const { assetsSummary } = useWalletAssets()
const rowClass = computed(() => (props.plain ? 'text-primary' : 'text-tertiary'))
const iconColor = computed(() => (props.plain ? 'primary' : 'tertiary'))
const rootClass = computed(() => {
if (props.boursecuLayout) {
return 'assets-section assets-section--boursecu rounded-lg px-3 py-3 mt-2'
}
if (props.plain) {
return 'py-2'
}
return 'bg-primary rounded-lg p-2 py-3 mt-2'
})
const meta = computed(() => assetsSummary.value?.meta)
const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? [])
const primaryMetalTotal = computed(() => metalTotals.value[0])
const cashWallet = computed(() => assetsSummary.value?.cash_wallet ?? 0)
const inventoryCards = computed(() => assetsSummary.value?.inventory_cards ?? [])
const countBasedInventory = computed(() =>
(assetsSummary.value?.count_products ?? []).reduce((sum, item) => sum + Number(item.quantity ?? 0), 0)
)
function formatMetalTotal(metal: WalletMetalTotal) {
return `${formatWeightAmount(resolveSummaryMetalWeight(metal, inventoryCards.value))}${metal.unit}`
}
</script>
@@ -4,12 +4,12 @@
:class="containerClass"
>
<template v-if="displayState !== 'open'">
<div class="status-box" :class="isBoursecu ? 'status-box--boursecu' : 'status-box--default'">
<div class="status-box status-box--boursecu">
<div class="status-box__icon-wrap">
<Icon
:icon="displayState === 'closed' ? 'vuesax-linear:lock' : 'vuesax-linear:info-circle'"
size="22"
:color="isBoursecu ? '#ffffff' : '#64748b'"
color="#ffffff"
/>
</div>
<div class="status-box__title">{{ statusTitle }}</div>
@@ -18,8 +18,8 @@
</template>
<template v-else>
<div class="price-label" :class="isBoursecu ? 'price-label--boursecu' : 'text-gray-600 text-[12px] ml-2 w-full text-center'">
{{ priceLabel }}
<div class="price-label price-label--boursecu">
قیمت لحظه‌ای کاتد مس
</div>
<div class="w-full items-center flex justify-center">
<div v-if="price" class="flex gap-1 text-xs items-center">
@@ -38,11 +38,10 @@
</div>
<Vue3Odometer
:value="displayGoldVal"
class="o-text !text-xl font-bold"
:class="isBoursecu ? 'price-odometer--boursecu' : ''"
class="o-text !text-xl font-bold price-odometer--boursecu"
style="direction: ltr"
/>
<span :class="isBoursecu ? 'price-unit--boursecu' : ''">ریال</span>
<span class="price-unit--boursecu">ریال</span>
</div>
</div>
</template>
@@ -52,9 +51,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { useConfigStore } from '@/store/configStore'
import { useUserStore } from '@/store/userStore'
import { isBoursecuProject } from '@/utils/config'
import { getTradeBuyPrice, lastPrice, percentPriceChanged, price } from '@/utils/price'
import {
NO_QUOTE_MESSAGE,
@@ -65,13 +62,7 @@ import Vue3Odometer from 'vue3-odometer'
import 'odometer/themes/odometer-theme-default.css'
import { formatSignedPercentChange } from '@/utils/usePrice'
const config = useConfigStore()
const userStore = useUserStore()
const isBoursecu = isBoursecuProject()
const priceLabel = computed(() =>
isBoursecu ? 'قیمت لحظه‌ای کاتد مس' : `قیمت خرید از ${config.config.name_fa}`
)
const displayState = computed(() => getPriceDisplayState())
@@ -91,9 +82,6 @@ const statusHint = computed(() => {
})
const containerClass = computed(() => {
if (!isBoursecu) {
return 'border-gray-200 border-1'
}
if (displayState.value !== 'open') {
return 'boursecu-price-box boursecu-price-box--closed'
}
@@ -190,18 +178,6 @@ void price
color: rgba(255, 255, 255, 0.78);
}
.status-box--default .status-box__icon-wrap {
background: #f1f5f9;
}
.status-box--default .status-box__title {
color: #334155;
}
.status-box--default .status-box__hint {
color: #64748b;
}
.price-change-badge {
display: inline-flex;
align-items: center;
@@ -1,6 +1,5 @@
<script setup lang="ts">
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { isBoursecuProject } from '@/utils/config'
import type { HomeQuickActionItem } from '../layouts/types'
defineProps<{
@@ -10,15 +9,12 @@ defineProps<{
defineEmits<{
click: []
}>()
const isBoursecu = isBoursecuProject()
</script>
<template>
<button
type="button"
class="home-quick-action-box flex flex-col items-center justify-center gap-2 rounded-xl border-2 p-1 min-h-[96px] w-full"
:class="{ 'home-quick-action-box--boursecu': isBoursecu }"
class="home-quick-action-box home-quick-action-box--boursecu flex flex-col items-center justify-center gap-2 rounded-xl border-2 p-1 min-h-[96px] w-full"
@click="$emit('click')"
>
<div class="flex h-12 w-12 shrink-0 items-center justify-center">
@@ -126,7 +126,6 @@ const { width: chartWidth } = useElementSize(chartContainerRef)
const seriesLength = computed(() => props.priceChartSeries?.[0]?.data?.length ?? 0)
const amplifyKatodChart = computed(() =>
shouldAmplifyKatodMesChart({
isBoursecu: props.layout === 'boursecu',
metalTypeSetting: settingsStore.metalTypeSettingValue,
})
)
@@ -141,7 +140,6 @@ const katodDayRange = computed(() => {
return resolveMetalDayRangePrices({
price: price.value,
metal: props.selectedMetal,
isBoursecuLayout: true,
metalRatio: selectedMetalChartRatio.value,
})
})
@@ -2,7 +2,7 @@
<div
class="w-full flex justify-evenly rounded-lg py-2 home_appear_fade_in"
:class="[
isBoursecuLayout ? 'mt-8 pt-2' : 'mt-6',
'mt-8 pt-2',
variant === 'mobile' ? 'pb-20' : 'mt-0 pb-0',
]"
>
@@ -34,7 +34,6 @@
import { computed } from 'vue'
import { usePriceWithoutType } from '../../../../utils/usePrice'
import { price } from '@/utils/price'
import { getHomeLayoutId } from '../layouts/registry'
import { resolveMetalDayRangePrices, usePriceChart } from '../composables/usePriceChart'
withDefaults(
@@ -47,13 +46,11 @@ withDefaults(
)
const { selectedChartMetal, selectedMetalChartRatio } = usePriceChart()
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
const rangePrices = computed(() =>
resolveMetalDayRangePrices({
price: price.value,
metal: selectedChartMetal.value,
isBoursecuLayout: isBoursecuLayout.value,
metalRatio: selectedMetalChartRatio.value,
})
)
+4 -27
View File
@@ -7,20 +7,16 @@ import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets'
import {
formatBoursecuMetalModalText,
formatMetalAssetsInfoText,
formatRialAssetsInfoText,
resolveAssetsRialValue,
} from '@/utils/productAssets'
import { useConfigStore } from '@/store/configStore'
import { useSettingsStore } from '@/store/settingsStore'
import { getTradeBuyPrice, lastPrice, stopLastPricePolling } from '@/utils/price'
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
import { message, Modal } from 'ant-design-vue'
import { getReferralShareText, getReferralShareTitle, shareReferralText } from '@/utils/referralShare'
import { useBanners } from './useBanners'
import { usePriceChart, type ChartMetalType } from './usePriceChart'
import type { HomeActionItem } from '../layouts/types'
import { getHomeLayoutId } from '../layouts/registry'
import { initHomePageData, resetHomePageData, useHomePageLoading } from './useHomePageData'
import { ensureAuthenticatedRoute } from '@/app/setupRouter'
import { resetWalletPageData } from '../../wallet/composables/useWalletPageData'
@@ -30,7 +26,6 @@ import { resetProductsPageData } from '../../products/composables/useProductsPag
export interface HomePageContext {
homePageLoading: ReturnType<typeof useHomePageLoading>['homePageLoading']
isGaroosi: ReturnType<typeof computed<boolean>>
bannerEnabled: ReturnType<typeof computed<boolean>>
showClosedMessage: ReturnType<typeof computed<boolean>>
closedMessage: ReturnType<typeof computed>
@@ -41,7 +36,6 @@ export interface HomePageContext {
updateChartData: ReturnType<typeof usePriceChart>['updateChartData']
selectedChartMetal: ReturnType<typeof usePriceChart>['selectedChartMetal']
setChartMetal: (metal: ChartMetalType) => void
isBoursecuHomeLayout: ReturnType<typeof computed<boolean>>
handleBannerClick: (banner: { typeClick?: string; modalText?: string; link?: string }) => void
openGoldModal: (
metal: WalletMetalTotal,
@@ -57,7 +51,6 @@ export const HOME_PAGE_CONTEXT_KEY: InjectionKey<HomePageContext> = Symbol('home
export function useHomePage() {
const userStore = useUserStore()
const walletStore = useWalletStore()
const configStore = useConfigStore()
const settingsStore = useSettingsStore()
const { assetsSummary, clearWalletAssets } = useWalletAssets()
@@ -76,9 +69,7 @@ export function useHomePage() {
selectedChartMetal,
setChartMetal,
} = usePriceChart()
const isBoursecuHomeLayout = computed(() => getHomeLayoutId() === 'boursecu')
const isGaroosi = computed(() => configStore.config?.name === 'garoosi')
const bannerEnabled = computed(() => settingsStore.isSettingTruthy('banner_enabled'))
const closedMessage = computed(() => userStore.getValueByKey('closed_message'))
const marketClosedText = computed(() => {
@@ -100,7 +91,7 @@ export function useHomePage() {
{
name: 'deposit',
icon: 'vuesax-linear:card-add',
label: isGaroosi.value ? 'واریز و برداشت' : 'واریز نقدی',
label: 'واریز نقدی',
handler: () => router.push({ name: 'wallet', query: { state: 'deposit' } }),
},
{
@@ -114,21 +105,11 @@ export function useHomePage() {
{
name: 'assets',
icon: 'vuesax-linear:cup',
label: isGaroosi.value ? 'دعوت از دوستان' : 'دارایی ها',
handler: () => (isGaroosi.value ? shareCode() : router.push({ name: 'fund' })),
label: 'دارایی ها',
handler: () => router.push({ name: 'fund' }),
},
])
async function shareCode() {
const text = getReferralShareText({
userName: userStore.userInfo?.name,
projectName: configStore.config.name,
referralId: userStore.userInfo?.referral_id,
})
await shareReferralText(text, getReferralShareTitle(configStore.config.name))
}
function copyCode() {
const code = userStore.userInfo?.referral_id
if (code) {
@@ -178,9 +159,7 @@ export function useHomePage() {
const title =
modalTitle ??
modalText ??
(isBoursecuHomeLayout.value
? formatBoursecuMetalModalText(metal)
: formatMetalAssetsInfoText(metal))
formatBoursecuMetalModalText(metal)
Modal.confirm({
title,
@@ -230,7 +209,6 @@ export function useHomePage() {
const context: HomePageContext = {
homePageLoading,
isGaroosi,
bannerEnabled,
showClosedMessage,
closedMessage: marketClosedText,
@@ -241,7 +219,6 @@ export function useHomePage() {
updateChartData,
selectedChartMetal,
setChartMetal,
isBoursecuHomeLayout,
handleBannerClick,
openGoldModal,
openRialModal,
@@ -2,7 +2,6 @@ import { ref } from 'vue'
import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
import { useWalletAssets } from '@/composables/useWalletAssets'
import { clearProductsCatalog } from '@/composables/useProductsCatalog'
import { getHomeLayoutId } from '../layouts/registry'
import { fetchHomeChartPrices, loadHomeMaftolOffset, resetHomePriceChart } from './usePriceChart'
import { loadHomeBanners, resetHomeBanners } from './useBanners'
import { loadHomeFunds, resetHomeFunds } from './useUserFunds'
@@ -24,10 +23,7 @@ export function isHomePageDataInitialized() {
export async function initHomePageData(force = false) {
if (!force && initialized.value) {
startLastPricePolling()
if (getHomeLayoutId() === 'boursecu') {
void loadHomeMaftolOffset(false)
}
void loadHomeMaftolOffset(false)
return
}
@@ -49,9 +45,7 @@ export async function initHomePageData(force = false) {
loadHomeBanners(force),
])
if (getHomeLayoutId() === 'boursecu') {
await loadHomeMaftolOffset(force)
}
await loadHomeMaftolOffset(force)
initialized.value = true
} catch (error) {
@@ -2,7 +2,6 @@ import { computed, ref, watch } from 'vue'
import { getAllPrices } from '@/api/home'
import type { Product } from '@/models/products'
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
import { getHomeLayoutId } from '../layouts/registry'
import { getProductBuyPrice, resolvePriceOffsetRatio } from '@/utils/productPrice'
import { isKatodMesGroup, isMaftolMesGroup, isShamshMesGroup } from '@/utils/productDealMethod'
import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price'
@@ -31,10 +30,6 @@ function resolveChartProducts(products: Product[]) {
/** نسبت مفتول به کاتد — reactive به قیمت لحظه‌ای و کاتالوگ محصولات */
const maftolChartRatio = computed(() => {
if (getHomeLayoutId() !== 'boursecu') {
return 0
}
void lastPrice.value?.price_last
const { katod, maftol } = resolveChartProducts(catalogProducts.value)
@@ -43,10 +38,6 @@ const maftolChartRatio = computed(() => {
/** نسبت شمش مس به کاتد */
const shamshChartRatio = computed(() => {
if (getHomeLayoutId() !== 'boursecu') {
return 0
}
void lastPrice.value?.price_last
const { katod, shamsh } = resolveChartProducts(catalogProducts.value)
@@ -100,13 +91,12 @@ function applyChartRatio(basePrice: number, ratio: number) {
export function resolveMetalDayRangePrices(options: {
price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined
metal: ChartMetalType
isBoursecuLayout: boolean
metalRatio: number
}) {
const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters<typeof getTradeBuyPrice>[0])
const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters<typeof getTradeSellPrice>[0])
if (!options.isBoursecuLayout || options.metal === 'katod') {
if (options.metal === 'katod') {
return { high: baseHigh, low: baseLow }
}
@@ -176,7 +166,7 @@ function buildChartPoints(items: Array<{ timestamp: number; value: number }>): C
function syncChartSeries() {
const ratio = selectedMetalChartRatio.value
const data =
getHomeLayoutId() === 'boursecu' && selectedChartMetal.value !== 'katod'
selectedChartMetal.value !== 'katod'
? rawChartPoints.value.map(
([timestamp, value]) =>
[timestamp, applyChartRatio(value, ratio)] as ChartPoint
@@ -221,11 +211,6 @@ async function fetchPrices(force = false) {
}
async function loadMetalChartOffsets(force = false) {
if (getHomeLayoutId() !== 'boursecu') {
syncChartSeries()
return
}
if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) {
return
}
@@ -258,7 +243,7 @@ function updateChartData(timeline: string) {
function setChartMetal(metal: ChartMetalType) {
selectedChartMetal.value = metal
if (metal !== 'katod' && getHomeLayoutId() === 'boursecu' && !catalogProducts.value.length) {
if (metal !== 'katod' && !catalogProducts.value.length) {
void loadMetalChartOffsets(true)
}
}
@@ -1,17 +1,11 @@
<template>
<BoursecuDesktopHome v-if="layoutId === 'boursecu'" @logout="$emit('logout')" />
<DefaultDesktopHome v-else @logout="$emit('logout')" />
<BoursecuDesktopHome @logout="$emit('logout')" />
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { getHomeLayoutId } from '../layouts/registry'
import BoursecuDesktopHome from './layouts/BoursecuDesktopHome.vue'
import DefaultDesktopHome from './layouts/DefaultDesktopHome.vue'
defineEmits<{
logout: []
}>()
const layoutId = computed(() => getHomeLayoutId())
</script>
@@ -13,6 +13,7 @@ defineEmits<{
<DesktopHomeCard title="دارایی‌های من">
<AssetsSection
plain
boursecu-layout
class="home-desktop-assets"
@open-gold-modal="$emit('openGoldModal', $event)"
@open-rial-modal="$emit('openRialModal')"
@@ -1,126 +1,11 @@
<script setup lang="ts">
import { computed } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import BoursecuAssetsSummary from '../../components/BoursecuAssetsSummary.vue'
import PortfolioProfitInsight from '../../components/PortfolioProfitInsight.vue'
import { formatWeightAmount, usePriceWithoutType } from '@/utils/usePrice'
import { useWalletAssets } from '@/composables/useWalletAssets'
import type { WalletMetalTotal } from '@/models/walletAssets'
import { resolveSummaryMetalWeight } from '@/utils/productAssets'
import { useHomePageContext } from '../../composables/useHomePage'
const emit = defineEmits<{
openGoldModal: [metal: WalletMetalTotal, modalText?: string, modalTitle?: string, modalContent?: string]
openRialModal: []
}>()
const { assetsSummary } = useWalletAssets()
const ctx = useHomePageContext()
const isBoursecuLayout = computed(() => ctx.isBoursecuHomeLayout.value)
const meta = computed(() => assetsSummary.value?.meta)
const metalTotals = computed(() => assetsSummary.value?.metal_totals ?? [])
const inventoryCards = computed(() => assetsSummary.value?.inventory_cards ?? [])
const cashWallet = computed(() => assetsSummary.value?.cash_wallet ?? 0)
const countBasedInventory = computed(() =>
(assetsSummary.value?.count_products ?? []).reduce((sum, item) => sum + Number(item.quantity ?? 0), 0),
)
type MetricItem = {
id: string
label: string
value: string
metal?: WalletMetalTotal
showInfo?: boolean
onInfo?: 'gold' | 'rial'
}
const metrics = computed<MetricItem[]>(() => {
const items: MetricItem[] = []
if (meta.value?.has_count_based_products) {
items.push({
id: 'products',
label: 'محصولات',
value: `${usePriceWithoutType(countBasedInventory.value, false)} عدد`,
})
}
if (meta.value?.single_metal && metalTotals.value[0]) {
const metal = metalTotals.value[0]
items.push({
id: `metal-${metal.metal_id}`,
label: metal.label,
value: formatMetalTotal(metal),
metal,
showInfo: true,
onInfo: 'gold',
})
} else {
metalTotals.value.forEach((metal, index) => {
items.push({
id: `metal-${metal.metal_id}`,
label: metal.label,
value: formatMetalTotal(metal),
metal,
showInfo: index === 0,
onInfo: 'gold',
})
})
}
items.push({
id: 'rial',
label: 'دارایی ریالی',
value: `${usePriceWithoutType(cashWallet.value)} ریال`,
showInfo: true,
onInfo: 'rial',
})
return items
})
function formatMetalTotal(metal: WalletMetalTotal) {
return `${formatWeightAmount(resolveSummaryMetalWeight(metal, inventoryCards.value))}${metal.unit}`
}
function handleInfo(metric: MetricItem) {
if (metric.onInfo === 'rial') {
emit('openRialModal')
return
}
if (metric.metal) {
emit('openGoldModal', metric.metal)
}
}
</script>
<template>
<BoursecuAssetsSummary
v-if="isBoursecuLayout"
variant="desktop"
icon-color="tertiary"
/>
<template v-else>
<PortfolioProfitInsight variant="desktop" />
<div class="dhome-metrics">
<article v-for="metric in metrics" :key="metric.id" class="dhome-metric-tile">
<div class="dhome-metric-tile__head">
<span class="dhome-metric-tile__label">{{ metric.label }}</span>
<button
v-if="metric.showInfo"
type="button"
class="dhome-metric-tile__info"
aria-label="اطلاعات بیشتر"
@click="handleInfo(metric)"
>
<BasicIcon icon="vuesax-linear:info-circle" size="14" />
</button>
</div>
<p class="dhome-metric-tile__value">{{ metric.value }}</p>
</article>
</div>
</template>
</template>
@@ -6,7 +6,7 @@ import DesktopHomeCard from '../components/DesktopHomeCard.vue'
const ctx = useHomePageContext()
const chartLayout = computed(() => (toValue(ctx.isBoursecuHomeLayout) ? 'boursecu' : 'default'))
const chartLayout = 'boursecu' as const
const priceChartSeries = computed(() => toValue(ctx.priceChartSeries))
const chartSelection = computed(() => toValue(ctx.chartSelection))
const selectedChartMetal = computed(() => toValue(ctx.selectedChartMetal))
@@ -15,7 +15,7 @@ const props = withDefaults(
const ctx = useHomePageContext()
const chartLayout = computed(() => (toValue(ctx.isBoursecuHomeLayout) ? 'boursecu' : 'default'))
const chartLayout = 'boursecu' as const
const priceChartSeries = computed(() => toValue(ctx.priceChartSeries))
const chartSelection = computed(() => toValue(ctx.chartSelection))
const selectedChartMetal = computed(() => toValue(ctx.selectedChartMetal))
@@ -1,30 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import { useUserStore } from '@/store/userStore'
import { usePriceWithoutType } from '@/utils/usePrice'
import DesktopHomeSection from './DesktopHomeSection.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const userStore = useUserStore()
</script>
<template>
<DesktopHomeSection v-if="ctx.isGaroosi.value" title="دارایی‌ها">
<div class="dhome-garoosi-assets">
<div class="dhome-garoosi-assets__item">
<span class="dhome-garoosi-assets__label">دارایی طلایی</span>
<span class="dhome-garoosi-assets__value">
{{ (userStore?.userInfo?.wallet?.count_gold ?? 0).toFixed(3) }} گرم
</span>
</div>
<div class="dhome-garoosi-assets__divider" />
<div class="dhome-garoosi-assets__item">
<span class="dhome-garoosi-assets__label">دارایی ریالی</span>
<span class="dhome-garoosi-assets__value">
{{ usePriceWithoutType(userStore?.userInfo?.wallet?.count_cash ?? 0) }} ریال
</span>
</div>
</div>
</DesktopHomeSection>
</template>
@@ -1,17 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import { isBoursecuProject } from '@/utils/config'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const isBoursecu = isBoursecuProject()
</script>
<template>
<div v-if="!isBoursecu && ctx.showClosedMessage.value" class="dhome-alerts">
<div
class="dhome-alert dhome-alert--info"
v-html="ctx.closedMessage.value"
/>
</div>
</template>
@@ -1,15 +1,9 @@
<template>
<div class="dhome" :class="{ 'dhome--boursecu': isBoursecu }">
<div class="dhome dhome--boursecu">
<div class="dhome__container">
<slot />
</div>
</div>
</template>
<script lang="ts" setup>
import { isBoursecuProject } from '@/utils/config'
const isBoursecu = isBoursecuProject()
</script>
<style lang="less" src="../styles/desktop-home.less"></style>
@@ -2,17 +2,17 @@
<div
class="dhome-surface dhome-hero-price"
:class="{
'dhome-hero-price--boursecu-open': isBoursecu && displayState === 'open',
'dhome-hero-price--boursecu-closed': isBoursecu && displayState !== 'open',
'dhome-hero-price--boursecu-open': displayState === 'open',
'dhome-hero-price--boursecu-closed': displayState !== 'open',
}"
>
<template v-if="displayState !== 'open'">
<div class="dhome-hero-price__closed" :class="{ 'dhome-hero-price__closed--default': !isBoursecu }">
<div class="dhome-hero-price__closed">
<div class="dhome-hero-price__closed-icon">
<BasicIcon
:icon="displayState === 'closed' ? 'vuesax-linear:lock' : 'vuesax-linear:info-circle'"
size="22"
:color="isBoursecu ? '#ffffff' : '#64748b'"
color="#ffffff"
/>
</div>
<p class="dhome-hero-price__closed-title">{{ statusTitle }}</p>
@@ -21,10 +21,9 @@
</template>
<template v-else>
<span class="dhome-label" :class="{ 'dhome-label--boursecu': isBoursecu }">
{{ isBoursecu ? 'قیمت لحظه‌ای کاتد مس' : 'قیمت لحظه‌ای' }}
<span class="dhome-label dhome-label--boursecu">
قیمت لحظه‌ای کاتد مس
</span>
<p v-if="!isBoursecu" class="dhome-hero-price__brand">{{ config.config?.name_fa }}</p>
<div class="dhome-hero-price__value-row">
<span
v-if="percentChange !== 0"
@@ -39,10 +38,9 @@
</span>
<Vue3Odometer
:value="displayGoldVal"
class="dhome-hero-price__odometer o-text"
:class="{ 'dhome-hero-price__odometer--boursecu': isBoursecu }"
class="dhome-hero-price__odometer o-text dhome-hero-price__odometer--boursecu"
/>
<span class="dhome-hero-price__unit" :class="{ 'dhome-hero-price__unit--boursecu': isBoursecu }">ریال</span>
<span class="dhome-hero-price__unit dhome-hero-price__unit--boursecu">ریال</span>
</div>
</template>
</div>
@@ -53,9 +51,7 @@ import { computed } from 'vue'
import Vue3Odometer from 'vue3-odometer'
import 'odometer/themes/odometer-theme-default.css'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { useConfigStore } from '@/store/configStore'
import { useUserStore } from '@/store/userStore'
import { isBoursecuProject } from '@/utils/config'
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
import {
NO_QUOTE_MESSAGE,
@@ -64,9 +60,7 @@ import {
} from '@/utils/storeHours'
import { formatSignedPercentChange } from '@/utils/usePrice'
const config = useConfigStore()
const userStore = useUserStore()
const isBoursecu = isBoursecuProject()
const displayState = computed(() => getPriceDisplayState())
@@ -152,18 +146,4 @@ const percentChange = computed(() => {
line-height: 1.7;
color: rgba(255, 255, 255, 0.78);
}
.dhome-hero-price__closed--default {
.dhome-hero-price__closed-icon {
background: #f1f5f9;
}
.dhome-hero-price__closed-title {
color: #334155;
}
.dhome-hero-price__closed-hint {
color: #64748b;
}
}
</style>
@@ -2,17 +2,14 @@
import { computed } from 'vue'
import { price } from '@/utils/price'
import { usePriceWithoutType } from '@/utils/usePrice'
import { getHomeLayoutId } from '../../layouts/registry'
import { resolveMetalDayRangePrices, usePriceChart } from '../../composables/usePriceChart'
const { selectedChartMetal, selectedMetalChartRatio } = usePriceChart()
const isBoursecuLayout = computed(() => getHomeLayoutId() === 'boursecu')
const rangePrices = computed(() =>
resolveMetalDayRangePrices({
price: price.value,
metal: selectedChartMetal.value,
isBoursecuLayout: isBoursecuLayout.value,
metalRatio: selectedMetalChartRatio.value,
})
)
@@ -1,14 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import DesktopHomeSection from './DesktopHomeSection.vue'
import ReferralSection from '../../components/ReferralSection.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
</script>
<template>
<DesktopHomeSection v-if="ctx.isGaroosi.value" title="دعوت از دوستان">
<ReferralSection />
</DesktopHomeSection>
</template>
@@ -7,7 +7,6 @@ import { useHomePageContext } from '../../composables/useHomePage'
import HomeSkeleton from '../../components/HomeSkeleton.vue'
import DesktopHomeShell from '../components/DesktopHomeShell.vue'
import DesktopHomeTopbar from '../components/DesktopHomeTopbar.vue'
import DesktopHomeAlerts from '../components/DesktopHomeAlerts.vue'
import DesktopLivePrice from '../components/DesktopLivePrice.vue'
import DesktopPriceRange from '../components/DesktopPriceRange.vue'
import DesktopAssetsMetrics from '../components/DesktopAssetsMetrics.vue'
@@ -29,7 +28,6 @@ const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
<template>
<DesktopHomeShell>
<DesktopHomeTopbar @logout="$emit('logout')" />
<DesktopHomeAlerts />
<HomeSkeleton v-if="showSkeleton" />
@@ -1,98 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/store/userStore'
import { getToken } from '@/utils'
import { useHomePageContext } from '../../composables/useHomePage'
import HomeSkeleton from '../../components/HomeSkeleton.vue'
import DesktopHomeShell from '../components/DesktopHomeShell.vue'
import DesktopHomeTopbar from '../components/DesktopHomeTopbar.vue'
import DesktopHomeAlerts from '../components/DesktopHomeAlerts.vue'
import DesktopLivePrice from '../components/DesktopLivePrice.vue'
import DesktopPriceRange from '../components/DesktopPriceRange.vue'
import DesktopAssetsMetrics from '../components/DesktopAssetsMetrics.vue'
import DesktopChartSection from '../components/DesktopChartSection.vue'
import DesktopDefaultActions from '../components/DesktopDefaultActions.vue'
import DesktopGaroosiAssets from '../components/DesktopGaroosiAssets.vue'
import DesktopReferralPanel from '../components/DesktopReferralPanel.vue'
import DesktopBannersPanel from '../components/DesktopBannersPanel.vue'
defineEmits<{
logout: []
}>()
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const ctx = useHomePageContext()
const showContent = computed(() => Boolean(userInfo.value))
const showSkeleton = computed(() => Boolean(getToken()) && !userInfo.value)
</script>
<template>
<DesktopHomeShell>
<DesktopHomeTopbar @logout="$emit('logout')" />
<DesktopHomeAlerts />
<HomeSkeleton v-if="showSkeleton" />
<template v-if="showContent">
<section v-if="ctx.isGaroosi.value" class="dhome-hero-row">
<DesktopLivePrice />
<DesktopPriceRange />
</section>
<div v-if="ctx.isGaroosi.value" class="dhome-default-grid">
<div class="dhome-default-grid__main">
<DesktopDefaultActions />
<section
v-if="ctx.bannerEnabled.value && ctx.banners.value?.length"
class="dhome-surface dhome-chart-banner-combined"
>
<header class="dhome-section__header">
<h2 class="dhome-section__title">نمودار قیمت</h2>
</header>
<div class="dhome-chart-banner-combined__grid">
<DesktopChartSection embedded />
<DesktopBannersPanel embedded />
</div>
</section>
<DesktopChartSection v-else />
</div>
<aside class="dhome-default-grid__side">
<DesktopGaroosiAssets />
<DesktopReferralPanel />
</aside>
</div>
<template v-else>
<DesktopDefaultActions />
<DesktopAssetsMetrics
@open-gold-modal="ctx.openGoldModal"
@open-rial-modal="ctx.openRialModal"
/>
<section class="dhome-hero-row">
<DesktopLivePrice />
<DesktopPriceRange />
</section>
<section
v-if="ctx.bannerEnabled.value && ctx.banners.value?.length"
class="dhome-surface dhome-chart-banner-combined"
>
<header class="dhome-section__header">
<h2 class="dhome-section__title">نمودار قیمت</h2>
</header>
<div class="dhome-chart-banner-combined__grid">
<DesktopChartSection embedded />
<DesktopBannersPanel embedded />
</div>
</section>
<DesktopChartSection v-else />
</template>
</template>
</DesktopHomeShell>
</template>
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,5 @@
import type { HomeLayoutConfig } from '../types'
/**
* چیدمان اختصاصی boursecu — فقط وقتی home_layout=boursecu یا پروژه boursecu باشد.
* شامل: دارایی بدون بک‌گراند، گرید ۶ باکس، نمودار کاتد/مفتول، و ...
*/
export const boursecuHomeLayoutConfig: HomeLayoutConfig = {
id: 'boursecu',
wrapperClass: 'rounded-lg mt-5',
@@ -13,7 +9,6 @@ export const boursecuHomeLayoutConfig: HomeLayoutConfig = {
{ id: 'boursecuQuickActions' },
],
sections: [
{ id: 'marketClosed' },
{ id: 'priceChart' },
{ id: 'priceStats' },
],
@@ -1,20 +0,0 @@
import type { HomeLayoutConfig } from '../types'
/** چیدمان پیش‌فرض — همه پروژه‌ها به‌جز boursecu */
export const defaultHomeLayoutConfig: HomeLayoutConfig = {
id: 'default',
wrapperClass: 'p-1 shadow-lg from-primary rounded-lg p-px to-transparent mt-5',
sections: [
{ id: 'garoosiAssets' },
{ id: 'mainActions' },
{ id: 'referral' },
{ id: 'assets' },
],
tailSections: [
{ id: 'marketClosed' },
{ id: 'currentPrice' },
{ id: 'banners' },
{ id: 'priceChart' },
{ id: 'priceStats' },
],
}
+3 -15
View File
@@ -1,13 +1,10 @@
import { defineAsyncComponent, type Component } from 'vue'
import { getConfigApp, isBoursecuProject } from '@/utils/config'
import type { HomeLayoutConfig, HomeLayoutId } from './types'
import { defaultHomeLayoutConfig } from './configs/default'
import { boursecuHomeLayoutConfig } from './configs/boursecu'
export const DEFAULT_HOME_LAYOUT_ID: HomeLayoutId = 'default'
export const DEFAULT_HOME_LAYOUT_ID: HomeLayoutId = 'boursecu'
const HOME_LAYOUT_CONFIGS: Record<HomeLayoutId, HomeLayoutConfig> = {
default: defaultHomeLayoutConfig,
boursecu: boursecuHomeLayoutConfig,
}
@@ -26,20 +23,11 @@ export function isHomeLayoutId(value: unknown): value is HomeLayoutId {
}
export function getHomeLayoutId(): HomeLayoutId {
const adminLayout = getConfigApp('home_layout')
if (isHomeLayoutId(adminLayout)) {
return adminLayout
}
if (isBoursecuProject()) {
return 'boursecu'
}
return DEFAULT_HOME_LAYOUT_ID
return 'boursecu'
}
export function getHomeLayoutConfig(layoutId = getHomeLayoutId()): HomeLayoutConfig {
return HOME_LAYOUT_CONFIGS[layoutId] ?? HOME_LAYOUT_CONFIGS[DEFAULT_HOME_LAYOUT_ID]
return HOME_LAYOUT_CONFIGS[layoutId] ?? boursecuHomeLayoutConfig
}
export function resolveHomeLayoutComponent(_layoutId = getHomeLayoutId()): Component {
@@ -1,16 +1,15 @@
<script setup lang="ts">
import { computed, inject } from 'vue'
import { inject } from 'vue'
import AssetsSection from '../../components/AssetsSection.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const isPlainAssets = computed(() => ctx.isBoursecuHomeLayout.value)
</script>
<template>
<AssetsSection
v-if="!ctx.isGaroosi.value"
:plain="isPlainAssets"
:boursecu-layout="isPlainAssets"
plain
boursecu-layout
@open-gold-modal="ctx.openGoldModal"
@open-rial-modal="ctx.openRialModal"
/>
@@ -1,23 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import { useUserStore } from '@/store/userStore'
import { usePriceWithoutType } from '@/utils/usePrice'
import AssetItem from '../../components/AssetItem.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const userStore = useUserStore()
</script>
<template>
<div v-if="ctx.isGaroosi.value" class="flex justify-between px-2 mb-2">
<AssetItem
label="دارایی طلایی:"
:value="`${(userStore?.userInfo?.wallet?.count_gold ?? 0).toFixed(3)} گرم`"
/>
<AssetItem
label="دارایی ریالی:"
:value="`${usePriceWithoutType(userStore?.userInfo?.wallet?.count_cash ?? 0)} ریال`"
/>
</div>
</template>
@@ -1,15 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import { isBoursecuProject } from '@/utils/config'
import MarketClosedMessage from '../../components/MarketClosedMessage.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
</script>
<template>
<MarketClosedMessage
v-if="!isBoursecuProject() && ctx.showClosedMessage.value"
:message="ctx.closedMessage.value as string"
/>
</template>
@@ -4,7 +4,7 @@ import PriceChart from '../../components/PriceChart.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
const layout = computed(() => (toValue(ctx.isBoursecuHomeLayout) ? 'boursecu' : 'default'))
const layout = 'boursecu' as const
const priceChartSeries = computed(() => toValue(ctx.priceChartSeries))
const chartSelection = computed(() => toValue(ctx.chartSelection))
const selectedChartMetal = computed(() => toValue(ctx.selectedChartMetal))
@@ -1,11 +0,0 @@
<script setup lang="ts">
import { inject } from 'vue'
import ReferralSection from '../../components/ReferralSection.vue'
import { HOME_PAGE_CONTEXT_KEY } from '../../composables/useHomePage'
const ctx = inject(HOME_PAGE_CONTEXT_KEY)!
</script>
<template>
<ReferralSection v-if="ctx.isGaroosi.value" />
</template>
@@ -1,23 +1,17 @@
import type { Component } from 'vue'
import type { HomeSectionId } from '../types'
import GaroosiAssetsSection from './GaroosiAssetsSection.vue'
import MainActionsSection from './MainActionsSection.vue'
import BoursecuQuickActionsSection from './BoursecuQuickActionsSection.vue'
import ReferralSectionBlock from './ReferralSectionBlock.vue'
import AssetsSectionBlock from './AssetsSectionBlock.vue'
import MarketClosedSection from './MarketClosedSection.vue'
import CurrentPriceSection from './CurrentPriceSection.vue'
import BannersSection from './BannersSection.vue'
import PriceChartSection from './PriceChartSection.vue'
import PriceStatsSection from './PriceStatsSection.vue'
export const HOME_SECTION_COMPONENTS: Record<HomeSectionId, Component> = {
garoosiAssets: GaroosiAssetsSection,
mainActions: MainActionsSection,
boursecuQuickActions: BoursecuQuickActionsSection,
referral: ReferralSectionBlock,
assets: AssetsSectionBlock,
marketClosed: MarketClosedSection,
currentPrice: CurrentPriceSection,
banners: BannersSection,
priceChart: PriceChartSection,
+2 -6
View File
@@ -1,13 +1,10 @@
/** چیدمان‌های صفحه اصلی — هر پروژه/ادمین می‌تواند یکی را انتخاب کند */
export type HomeLayoutId = 'default' | 'boursecu'
/** چیدمان صفحه اصلی boursecu */
export type HomeLayoutId = 'boursecu'
export type HomeSectionId =
| 'garoosiAssets'
| 'mainActions'
| 'boursecuQuickActions'
| 'referral'
| 'assets'
| 'marketClosed'
| 'currentPrice'
| 'banners'
| 'priceChart'
@@ -31,7 +28,6 @@ export interface HomeLayoutConfig {
wrapperClass?: string
outerSections?: HomeLayoutSectionConfig[]
sections: HomeLayoutSectionConfig[]
/** بعد از wrapper — مثل قیمت و نمودار در UI قدیمی */
tailSections?: HomeLayoutSectionConfig[]
}
+1 -11
View File
@@ -15,10 +15,6 @@
v-if="usesQuantityTrade(selectedProduct)"
:ProductItem="selectedProduct"
/>
<CopperSilver
v-else-if="isMetalCommodityGroup(selectedProduct?.group_type) && !isBoursecuDealProductGroup(selectedProduct?.group_type)"
:ProductItem="selectedProduct"
/>
<DealProduct v-else :ProductItem="selectedProduct" @ordered="onProductOrdered" />
</div>
</BasicActionSheet>
@@ -36,10 +32,6 @@
v-if="usesQuantityTrade(selectedProduct)"
:ProductItem="selectedProduct"
/>
<CopperSilver
v-else-if="isMetalCommodityGroup(selectedProduct?.group_type) && !isBoursecuDealProductGroup(selectedProduct?.group_type)"
:ProductItem="selectedProduct"
/>
<DealProduct v-else :ProductItem="selectedProduct" @ordered="onProductOrdered" />
</div>
</BasicModal>
@@ -78,13 +70,11 @@
<script setup lang="ts">
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import indexGold from '@/views/base/gold/index.vue'
import CopperSilver from '@/views/base/copper&silver/index.vue'
import DealProduct from '@/views/base/products/components/DealProduct.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import ResponsiveViewPair from '@/layouts/components/ResponsiveViewPair.vue'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { isMetalCommodityGroup, usesQuantityTrade } from '@/utils/productCategories'
import { isBoursecuDealProductGroup } from '@/utils/productDealMethod'
import { usesQuantityTrade } from '@/utils/productCategories'
import { useProductsPageProvider } from './composables/useProductsPage'
import MobileProductsView from './mobile/MobileProductsView.vue'
import DesktopProductsView from './desktop/DesktopProductsView.vue'
@@ -1,18 +1,16 @@
import { computed, inject, provide, type ComputedRef, type InjectionKey } from 'vue'
import { computed, inject, provide, type InjectionKey } from 'vue'
import { useConfigStore } from '@/store/configStore'
import {
getProjectBaleLink,
getProjectSupportPhoneLink,
isBoursecuProject,
} from '@/utils/config'
import { getRulesBlocks, getRulesTitle, type RulesBlock } from '../rulesContent'
export interface RulesPageContext {
config: ReturnType<typeof useConfigStore>
nameProject: ComputedRef<string>
isBoursecu: ComputedRef<boolean>
rulesTitle: ComputedRef<string>
rulesBlocks: ComputedRef<RulesBlock[]>
nameProject: ReturnType<typeof computed<string>>
rulesTitle: ReturnType<typeof computed<string>>
rulesBlocks: ReturnType<typeof computed<RulesBlock[]>>
getProjectSupportPhoneLink: typeof getProjectSupportPhoneLink
getProjectBaleLink: typeof getProjectBaleLink
}
@@ -26,16 +24,13 @@ export function useRulesPage() {
() => config.config?.name_fa || config.config?.name || 'بورس مس',
)
const isBoursecu = computed(() => isBoursecuProject())
const rulesTitle = computed(() => getRulesTitle(nameProject.value, true))
const rulesTitle = computed(() => getRulesTitle(nameProject.value, isBoursecu.value))
const rulesBlocks = computed(() => getRulesBlocks(nameProject.value, isBoursecu.value))
const rulesBlocks = computed(() => getRulesBlocks(nameProject.value, true))
const context: RulesPageContext = {
config,
nameProject,
isBoursecu,
rulesTitle,
rulesBlocks,
getProjectSupportPhoneLink,
-44
View File
@@ -1,5 +1,3 @@
import { isBazretalaProject } from '@/utils/config'
export interface RulesBlock {
title?: string
items: string[]
@@ -41,40 +39,6 @@ function getDefaultRulesItems(nameProject: string): string[] {
]
}
function getBazretalaRulesBlocks(): RulesBlock[] {
return [
{
items: [
'کاربران موظفند قبل از ثبت‌ نام، تمامی مفاد این تعهدنامه را مطالعه نموده و صرفاً در صورت پذیرش آن اقدام به ثبت‌نام نمایند.',
'کاربران بذر طلا می‌پذیرند که کلیه‌ی فعالیت‌های آن‌ها در بذر طلا، در چارچوب قوانین جمهوری اسلامی ایران بوده و هیچ گونه فعالیتی خارج از این چارچوب انجام نخواهد شد.',
'احراز هویت برای استفاده از خدمات بذر طلا ضروری بوده و کاربران موظفند اطلاعات صحیح خود را بارگذاری نمایند؛ بدیهی است در صورت وجود هرگونه تخلف در احراز هویت، مسئولیت به عهده‌ی فرد متخلف بوده و بذر طلا حق توقف ارائه خدمات به کاربر مذکور و ارجاع موارد تخلف به مراجع ذی‌صلاح را خواهد داشت.',
'بذر طلا خود را ملزم به حفظ اطلاعات شخصی کاربران در چارچوب قوانین قضایی کشور می‌داند.',
'کاربران بذر طلا تعهد می‌دهند که از قرار دادن حساب خود در اختیار دیگران خودداری نمایند. از خدمات سایت، تنها برای حساب شخصی خود استفاده نموده و مسئولیت استفاده از خدمات بذر طلا برای فردی دیگر، به عهده کاربر خواهد بود. شماره حساب اعلام شده به سایت باید متعلق به کاربر بوده و کاربران مجاز به دادن شماره حساب متعلق به اشخاص دیگر نیستند.',
'کاربران می‌پذیرند در صورت تشخیص کارشناسان پشتیبانی، جهت حفظ امنیت دارایی حساب کاربری‌شان با اطلاع قبلی نسبت به برقراری تماس تصویری با تیم پشتیبان همکاری نمایند.',
'هرگونه سهل‌انگاری کاربران در حفظ اطلاعات شخصی و امنیتی از جمله: رمز عبور کاربر، سرقت گوشی کاربر، سرقت کارت ملی و مدارک شناسایی کاربر، سپردن مدارک شخصی و موبایل به فرد ثالث و ... به عهده‌ی شخص کاربر بوده و بذر طلا هیچ گونه مسئولیتی در این موارد نخواهد داشت.',
'اکیداً توصیه می‌شود از رمز عبوری پیچیده، امن و همچنین کد شناسایی دو عاملی استفاده شود.',
'بذر طلا این اطمینان را می‌دهد که دارایی‌های کاربران را نزد خود به امانت و به بهترین شکل و با بالاترین استانداردهای امنیتی ممکن حفظ نماید؛ در صورت بروز هرگونه مشکل امنیتی، بذر طلا متعهد به جبران خسارت خواهد بود.',
'به عنوان پلتفرم معاملات برخط فلزات گران‌بها، هیچ‌گونه مسئولیتی در قبال تصمیمات اقتصادی فی مابین کاربران و سود و زیان حاصل از آن بر عهده بذر طلا نمی‌باشد.',
'بذر طلا حق اصلاح و تغییر قوانین و مقررات ذکر شده در این صفحه را چه به صورت کلی و چه به صورت جزئی دارد و نسخه نهایی را در این صفحه در معرض دید قرار می‌دهد. استفاده از خدمات بذر طلا پس از اعمال تغییرات، به معنای پذیرش همه تغییرات می‌باشد.',
'از لحظه خرید کاربران تا زمان تحویل طلا و نقره ی خریداری شده، مسئولیت حفظ و نگهداری اصل طلا و نقره بر عهده بذر طلا می‌باشد.',
'کاربران بذر طلا اذعان می‌دارند که از ریسک‌های اقتصادی مربوط به سرمایه‌گذاری در حوزه طلا و نقره مطلع بوده و با علم بر این موضوع اقدام به تبادل و سرمایه‌گذاری می‌کنند.',
'بذر طلا متعهد می‌شود در حد توان از حریم شخصی شما محافظت کند و در این راستا، تکنولوژی مورد نیاز در سایت و پلتفرم را برای هر چه مطمئن‌تر و امن‌تر شدن استفاده شما، توسعه دهد.',
'در صورت بروز نوسان شدید در قیمت طلا و نقره هرگونه رویداد برهم‌زننده نظم حاکم بر بازار طلا و نقره، بذر طلا می‌تواند برای خرید و فروش طلا و نقره توسط کاربر سقف تعیین کند تا دو طرف متضرر نشوند.',
'بذر طلا مجاز است بدون اطلاع قبلی و به صلاح‌دید شخصی نسبت به توقف سفارش‌گیری جدید، معاملات (از جمله سفارش‌های باز) را متوقف کند؛ بدیهی است که کلیه سفارش‌های تکمیل‌شده قبل از توقف سفارش‌گیری، پردازش و تحویل داده خواهد شد.',
'در صورت بروز هرگونه اختلال در اینترنت و خدمات زیرساخت به دلایل حاکمیتی، هیچ‌گونه مسئولیتی به عهده بذر طلا نمی‌باشد.',
'درخواست برداشت ریال از حساب کاربری در سریع‌ترین زمان ممکن پس از ثبت، بررسی خواهد شد. زمان واریز پول به حساب کاربران بر اساس محدودیت‌های انتقال وجه بین بانکی، متفاوت خواهد بود. برای اطلاعات بیشتر، به قوانین انتقال وجه بین بانکی (پایا، ساتنا) مراجعه فرمایید.',
'کاربر می‌پذیرد که به جز در موارد مورد تعهد، حق هیچ‌گونه داعیه، طلب و شکایت از سایت، مدیران، کارمندان و افراد مرتبط با این سایت را نخواهد داشت.',
'اگر کاربر تحت هر عنوان اشتباهاً یا من غیر حق، وجوهی را به حساب کاربر منظور یا در محاسبات خود هر نوع اشتباهی نماید، بذر طلا هر زمان مجاز و مختار است راساً و مستقلاً و بدون انجام هیچ‌گونه تشریفات اداری و قضایی و دریافت اجازه کتبی از متعهد (صاحب حساب) در رفع اشتباه و برداشت از حساب‌های وی اقدام نماید و تشخیص نسبت به وقوع اشتباه یا پرداخت بدون حق و لزوم برداشت از حساب معتبر خواهد بود و کاربر حق هرگونه اعتراض و ادعایی را در خصوص نحوه عملکرد بذر طلا از هر جهت از خود ساقط می‌نماید.',
'در صورت بروز هرگونه مشکل یا ابهام در هر یک از معاملات (مانند دستکاری قیمت، اختلال در وب‌سرویس‌ها، انجام معامله خارج از دامنه نوسان و ...)، بذر طلا حق دارد مستقلاً آن معامله را ابطال کرده و دارایی‌های هر یک از طرفین را به حساب خودشان عودت دهد. بدیهی است در صورتی که اشکال از سمت بذر طلا باشد، شرکت موظف خواهد بود که جبران خسارت نماید و در غیر این صورت، کاربر حق هرگونه اعتراض و ادعایی در خصوص نحوه عملکرد از هر جهت را از خود ساقط می‌نماید.',
'در صورت فوت یا حجر کاربر، بذر طلا پیرو قوانین جمهوری اسلامی ایران می‌باشد. کاربر با علم و اطلاع از مقررات این بند نسبت به افتتاح حساب کاربری در بذر طلا اقدام نموده و کاربر، اشخاص ثالث، ورثه یا قائم‌مقام قانونی ایشان حق اعتراض و یا ادعایی در آینده در این خصوص نخواهند داشت.',
'هزینه خدمات و کارمزد تراکنش‌های مالی انجام شده در مطابق با توضیحات ارائه شده در صفحه راهنمای استفاده از بذر طلا می‌باشد.',
'تمامی علائم تجاری، طرح‌های گرافیکی، رابط کاربری، رابط بصری، عکس، لوگو، کدهای نرم‌افزار، ساختار، نحوه بیان، تنظیم مطالب و محتوای موجود در بذر طلا و شبکه‌های متعلق به بذر طلا و به‌طور کلی، سایر حقوق و امتیازات مربوطه منحصراً در مالکیت بذر طلا می‌باشد و هیچ شخصی حق استفاده از آن را بدون رضایت بذر طلا ندارد.',
'اگر هر یک از مفاد این موافقت‌نامه بر اساس هر قانون لازم‌الاجرا، نامعتبر یا غیرقابل اجرا تشخیص داده شود، این امر بر اعتبار هیچ ماده دیگری تأثیری نخواهد داشت و بقیه مقررات اجرا می‌شود.',
],
},
]
}
function getBoursecuRulesBlocks(nameProject: string): RulesBlock[] {
const brand = resolveProjectName(nameProject)
@@ -141,10 +105,6 @@ export function getRulesTitle(nameProject: string, isBoursecu: boolean) {
return `قوانین و مقررات معاملات مس در بستر ${brand}`
}
if (isBazretalaProject()) {
return 'قوانین و مقررات معاملات طلا و نقره در بستر بذر طلا'
}
return `قوانین و مقررات معاملات طلا در بستر ${brand}`
}
@@ -153,10 +113,6 @@ export function getRulesBlocks(nameProject: string, isBoursecu: boolean): RulesB
return getBoursecuRulesBlocks(nameProject)
}
if (isBazretalaProject()) {
return getBazretalaRulesBlocks()
}
return [{ items: getDefaultRulesItems(nameProject) }]
}
@@ -1,72 +0,0 @@
<template>
<div class="relative">
<div
class="mobile-page-bg mobile-page-bg--cover opacity-60"
aria-hidden="true"
:style="{ backgroundImage: `url(${img})` }"
></div>
<MobilePageHeader title="تراکنش ها">
<template #trailing>
<BasicIcon icon="vuesax-linear:filter" @click="showFilter = true" />
</template>
</MobilePageHeader>
<!-- <div ref="el" :class="loading ? 'h-screen' : ''" class="px-3 mpage-content pb-10">-->
<div ref="el" class="transactions-page-body relative z-1 px-3 mpage-content pb-24">
<list-anim v-if="!loading">
<Item v-for="item in items" :key="item.id" :item="item" />
<div :ref="bindMobileObserver" v-show="!loading" class="w-full opacity-0 relative">
در حال بارگذاری
</div>
<TransactionSkeleton v-if="loadingMore" class="mt-1 mb-8" />
</list-anim>
<TransactionSkeleton v-for="i in 13" v-else :key="i" class="mt-1" />
<div v-if="items.length <= 0 && !loading" class="flex flex-col items-center justify-center h-80vh">
<DotLottieVue
class="w-11rem h-11rem"
autoplay
src="https://lottie.host/51d3315a-cf88-4c18-bb4d-a31511fd4325/KjSBzcMbi6.json"
/>
<div class="font-bold">{{ emptyText }}</div>
</div>
</div>
<BasicActionSheet v-model:show="showFilter">
<div class="font-bold text-lg">فیلتر</div>
<div class="pb-10 mt-5">
<a-form-item class="w-full gap-2 !flex-col" label="تاریخ" name="password">
<BaseDatePicker v-model:value="dateFilter" class="w-full" size="large" />
</a-form-item>
<BasicButton class="w-full mt-5" type="primary" @click="applyFilter">اعمال فیلتر</BasicButton>
<BasicButton class="w-full mt-2" @click="showFilter = false">بازگشت</BasicButton>
</div>
</BasicActionSheet>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { loading, loadingMore } from '@/utils'
import Item from './components/Item.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
import img from '@/assets/img/user_ly1.svg'
import { useTransactionsPageContext } from './composables/useTransactionsPage'
const el = ref<HTMLElement | null>(null)
const {
items,
showFilter,
dateFilter,
emptyText,
applyFilter,
bindMobileObserver,
} = useTransactionsPageContext()
</script>
@@ -15,9 +15,6 @@
<p class="dtxn-row__title">{{ props.item.title }}</p>
<div class="dtxn-row__meta">
<span>{{ toJalaliOutOfUTC(props.item.expire_at) }}</span>
<span v-if="!isBoursecu" :class="goldAmount >= 0 ? 'text-green-600' : 'text-red-600'">
{{ formatTransactionGoldAmount(props.item, goldAmount) }} {{ props.item.unit }}
</span>
<span v-if="isGold">
قیمت معامله:
{{ usePriceWithoutType(tradePrice) }}
@@ -54,15 +51,7 @@
<div>{{ props.item.title }}</div>
<div>{{ toJalaliOutOfUTC(props.item.expire_at) }}</div>
</div>
<div :class="['flex mt-2', isBoursecu ? 'justify-end' : 'justify-between']">
<div
v-if="!isBoursecu"
:class="goldAmount >= 0 ? 'text-green-600' : 'text-red-600'"
class="text-gray-500 text-xs number-ltr"
>
{{ formatTransactionGoldAmount(props.item, goldAmount) }}
<div>{{ props.item.unit }}</div>
</div>
<div class="flex mt-2 justify-end">
<div class="text-gray-500 flex gap-1 text-xs items-center number-ltr">
<div :class="props.item.cash > 0 ? 'text-green-600' : 'text-red-600'" class="text-base">
{{ usePriceWithoutType(props.item.cash ?? 0) }}
@@ -116,23 +105,18 @@
<script lang="ts" setup>
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import {
getTransactionGoldAmount,
getTransactionTradePrice,
resolveTransactionStatusLabel,
usePriceWithoutType,
} from '@/utils/usePrice'
import { formatTransactionGoldAmount } from '@/utils/productDealMethod'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import { computed, ref } from 'vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import type { Transaction } from '@/models/transaction'
import { toJalaliOutOfUTC } from '@/utils/useDateTime'
import { isBoursecuProject } from '@/utils/config'
import TransactionDetailPanel from './TransactionDetailPanel.vue'
const isBoursecu = isBoursecuProject()
const infoVisible = ref(false)
const props = withDefaults(
@@ -143,7 +127,6 @@ const props = withDefaults(
{ variant: 'mobile' },
)
const goldAmount = computed(() => getTransactionGoldAmount(props.item))
const tradePrice = computed(() => getTransactionTradePrice(props.item))
const statusLabel = computed(() => resolveTransactionStatusLabel(props.item))
@@ -28,7 +28,6 @@ import {
transactionProductFilter,
transactionTableOutput,
transactionTradeSide,
transactionsBoursecuMode,
} from './useTransactionsPageData'
export interface TransactionsPageContext {
@@ -64,8 +63,7 @@ export interface TransactionsPageContext {
export const TRANSACTIONS_PAGE_KEY: InjectionKey<TransactionsPageContext> =
Symbol('transactionsPage')
export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
const isBoursecu = options?.boursecu ?? false
export function useTransactionsPageProvider() {
const isDesktop = useDesktopBreakpoint()
const showFilter = ref(false)
const mobileObserverEl = ref<HTMLElement | null>(null)
@@ -93,14 +91,10 @@ export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
)
const showProductFilter = computed(
() => transactionsBoursecuMode.value && boursecuTabSupportsProductFilter(activeTab.value),
() => boursecuTabSupportsProductFilter(activeTab.value),
)
const emptyText = computed(() => {
if (!transactionsBoursecuMode.value) {
return 'تراکنشی موجود نیست'
}
if (activeTab.value.id === 'trades' && transactionTradeSide.value === 'sell') {
return 'معامله فروشی برای شما به ثبت نرسیده است'
}
@@ -183,10 +177,8 @@ export function useTransactionsPageProvider(options?: { boursecu?: boolean }) {
)
onMounted(async () => {
if (isBoursecu) {
await getProductsCatalog()
}
await initTransactionsPageData(isBoursecu)
await getProductsCatalog()
await initTransactionsPageData()
})
onActivated(async () => {
@@ -236,6 +228,6 @@ export function useTransactionsPageContext() {
}
/** @deprecated Use useTransactionsPageProvider in index + useTransactionsPageContext in views. */
export function useTransactionsPage(options?: { boursecu?: boolean }) {
return useTransactionsPageProvider(options)
export function useTransactionsPage() {
return useTransactionsPageProvider()
}
@@ -14,7 +14,7 @@ import {
/** Shared transactions page state — refetched on every page visit (no list cache). */
let activeFetchId = 0
export const transactionsBoursecuMode = ref(false)
export const transactionsBoursecuMode = ref(true)
export const transactionPagination = ref<TableEngineInput>({ filters: [], limit: 7, page: 0 })
export const transactionItems = ref<Transaction[]>([])
export const transactionTableOutput = ref<TableEngineOutput<Transaction> | null>(null)
@@ -34,37 +34,19 @@ export function getActiveBoursecuTab(): BoursecuTransactionTab {
}
export function resetTransactionsPagination() {
if (transactionsBoursecuMode.value) {
transactionPagination.value = buildBoursecuTransactionPagination(
getActiveBoursecuTab(),
transactionTradeSide.value,
transactionDateFromFilter.value,
transactionDateToFilter.value,
transactionStatusFilter.value,
transactionProductFilter.value,
)
return
}
transactionPagination.value = { filters: [], limit: 7, page: 0 }
if (transactionDateFilter.value) {
transactionPagination.value.filters = [
{
field: 'created_at',
cr: 'and',
type: 'date',
op: 'eq',
value: transactionDateFilter.value,
},
]
}
transactionPagination.value = buildBoursecuTransactionPagination(
getActiveBoursecuTab(),
transactionTradeSide.value,
transactionDateFromFilter.value,
transactionDateToFilter.value,
transactionStatusFilter.value,
transactionProductFilter.value,
)
}
function resolveTransactionFetcher() {
const activeTab = getActiveBoursecuTab()
return transactionsBoursecuMode.value && activeTab.fetchMode === 'deposit'
? getTransactionTable2
: getTransactionTable
return activeTab.fetchMode === 'deposit' ? getTransactionTable2 : getTransactionTable
}
async function fetchTransactionsPage(append: boolean): Promise<boolean> {
@@ -134,8 +116,8 @@ export async function loadMoreTransactions() {
}
/** هر بار ورود به صفحه تراکنش‌ها */
export async function initTransactionsPageData(boursecu: boolean) {
transactionsBoursecuMode.value = boursecu
export async function initTransactionsPageData() {
transactionsBoursecuMode.value = true
await reloadTransactionsTable()
}
@@ -143,7 +125,7 @@ export function resetTransactionsPageData() {
activeFetchId += 1
loading.value = false
loadingMore.value = false
transactionsBoursecuMode.value = false
transactionsBoursecuMode.value = true
transactionPagination.value = { filters: [], limit: 7, page: 0 }
transactionItems.value = []
transactionTableOutput.value = null
@@ -1,84 +0,0 @@
<template>
<div class="dtxn">
<div class="dtxn__container">
<DesktopPageTopbar title="تراکنش‌ها" />
<div class="dtxn-filters">
<div class="dtxn-filters__label">
<BasicIcon icon="vuesax-linear:filter" size="16" />
<span>فیلتر تاریخ:</span>
</div>
<BaseDatePicker
v-model:value="dateFilter"
class="dtxn-filters__date"
placeholder="تاریخ"
size="large"
/>
<BasicButton type="primary" @click="applyFilter">اعمال فیلتر</BasicButton>
<BasicButton v-if="dateFilter" @click="clearFilter">حذف فیلتر</BasicButton>
</div>
<div ref="el" :class="loading ? 'min-h-[40vh]' : ''" class="mt-5">
<div v-if="!loading" class="dtxn-list">
<list-anim>
<Item
v-for="item in items"
:key="item.id"
:item="item"
variant="desktop"
/>
</list-anim>
<div :ref="bindDesktopObserver" class="w-full opacity-0 relative">
در حال بارگذاری
</div>
<TransactionSkeleton v-if="loadingMore" class="mt-1 mb-8" />
</div>
<template v-else>
<TransactionSkeleton v-for="i in 8" :key="i" class="mt-1" />
</template>
<div v-if="items.length <= 0 && !loading" class="dtxn-empty">
<DotLottieVue
class="w-44 h-44"
autoplay
src="https://lottie.host/51d3315a-cf88-4c18-bb4d-a31511fd4325/KjSBzcMbi6.json"
/>
<p class="dtxn-empty__text">{{ emptyText }}</p>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { loading, loadingMore } from '@/utils'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BaseDatePicker from '@/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue'
import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
import Item from '../components/Item.vue'
import { useTransactionsPageContext } from '../composables/useTransactionsPage'
const el = ref<HTMLElement | null>(null)
const {
items,
dateFilter,
emptyText,
applyFilter,
reloadTable,
bindDesktopObserver,
} = useTransactionsPageContext()
async function clearFilter() {
dateFilter.value = undefined
await reloadTable()
}
</script>
<style lang="less" src="./styles/desktop-transactions.less"></style>
@@ -39,11 +39,13 @@
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
&:hover {
border-color: #d1d5db;
color: #374151;
background: @App-primary-color;
border-color: @App-primary-color;
color: #fff;
}
&--active {
&--active,
&--active:hover {
background: @App-primary-color;
border-color: @App-primary-color;
color: #fff;
+2 -15
View File
@@ -1,6 +1,6 @@
<template>
<div class="transactions-page-root">
<ResponsiveViewPair v-if="isBoursecu">
<ResponsiveViewPair>
<template #mobile>
<BoursecuTransactionsPage />
</template>
@@ -8,29 +8,16 @@
<DesktopBoursecuTransactionsView />
</template>
</ResponsiveViewPair>
<ResponsiveViewPair v-else>
<template #mobile>
<DefaultTransactionsPage />
</template>
<template #desktop>
<DesktopDefaultTransactionsView />
</template>
</ResponsiveViewPair>
</div>
</template>
<script lang="ts" setup>
import ResponsiveViewPair from '@/layouts/components/ResponsiveViewPair.vue'
import { isBoursecuProject } from '@/utils/config'
import { useTransactionsPageProvider } from './composables/useTransactionsPage'
import BoursecuTransactionsPage from './BoursecuTransactionsPage.vue'
import DefaultTransactionsPage from './DefaultTransactionsPage.vue'
import DesktopBoursecuTransactionsView from './desktop/DesktopBoursecuTransactionsView.vue'
import DesktopDefaultTransactionsView from './desktop/DesktopDefaultTransactionsView.vue'
const isBoursecu = isBoursecuProject()
useTransactionsPageProvider({ boursecu: isBoursecu })
useTransactionsPageProvider()
</script>
<style scoped>
+6 -24
View File
@@ -14,7 +14,7 @@ import { updateMe, updatePass } from '@/api/me'
import { useUserStore } from '@/store/userStore'
import { getUserAvatarUrl } from '@/utils/file'
import { useConfigStore } from '@/store/configStore'
import { getConfigApp, isBoursecuProject, getBoursecuActionIconSrc } from '@/utils/config'
import { getBoursecuActionIconSrc } from '@/utils/config'
import { getReferralShareText, getReferralShareTitle, shareReferralText } from '@/utils/referralShare'
import { normalizeAuthFormFields } from '@/utils/persianDigits'
@@ -95,13 +95,9 @@ export function useUserPage() {
return result
})
const shareButtonLabel = computed(() =>
getConfigApp('name') === 'garoosi' ? 'دعوت از دوستان' : 'اشتراک گذاری',
)
const shareButtonLabel = computed(() => 'اشتراک گذاری')
const menuActions = computed<UserMenuAction[]>(() => {
const projectName = getConfigApp('name')
return [
const menuActions = computed<UserMenuAction[]>(() => [
{
id: 'password',
label: 'تغییر رمز عبور',
@@ -130,20 +126,13 @@ export function useUserPage() {
onClick: () => router.push({ name: 'about' }),
visible: true,
},
{
id: 'giftCard',
label: 'ثبت کارت هدیه',
icon: 'vuesax-linear:gift',
onClick: () => router.push({ name: 'giftCard' }),
visible: projectName === 'garoosi',
},
{
id: 'physicalReceive',
label: 'تحویل فیزیکی',
icon: 'vuesax-linear:direct-inbox',
iconSrc: isBoursecuProject() ? getBoursecuActionIconSrc('physicalReceive') : undefined,
iconSrc: getBoursecuActionIconSrc('physicalReceive'),
onClick: () => router.push({ name: 'physicalReceive' }),
visible: projectName !== 'bazretala',
visible: true,
},
{
id: 'rules',
@@ -152,15 +141,8 @@ export function useUserPage() {
onClick: () => router.push({ name: 'rules' }),
visible: true,
},
{
id: 'permissions',
label: 'مجوز ها',
icon: 'vuesax-linear:archive-book',
onClick: () => router.push({ name: 'permissions' }),
visible: projectName === 'bazretala',
},
].filter((action) => action.visible)
})
)
function setForm() {
userFormState.value = { ...userStore.userInfo } as User
@@ -13,10 +13,7 @@ import type { Card } from '@/models/wallet'
import { useUserStore } from '@/store/userStore'
import { useConfigStore } from '@/store/configStore'
import {
getProjectBaleContactLink,
getWalletDepositQuickAmountsRial,
isBazretalaProject,
isBoursecuProject,
} from '@/utils/config'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import {
@@ -60,7 +57,6 @@ export interface WalletPageContext {
depositGuideSupportLink: ReturnType<typeof computed<string>>
depositSmsSupportLink: ReturnType<typeof computed<string>>
depositQuickAmounts: ReturnType<typeof computed<readonly number[]>>
isBazretalaProject: typeof isBazretalaProject
usePriceWithoutType: typeof usePriceWithoutType
formatCashWalletBalanceText: typeof formatCashWalletBalanceText
setIsDeposit: (val: boolean) => void
@@ -100,16 +96,12 @@ export function useWalletPage() {
const depositQuickAmounts = computed(() => getWalletDepositQuickAmountsRial())
const depositGuideSupportLink = computed(() => {
if (config.config?.name === 'garoosi' || isBazretalaProject()) {
return getProjectBaleContactLink()
}
return buildSupportMessengerUrl(
const depositGuideSupportLink = computed(() =>
buildSupportMessengerUrl(
supportMessengerSettings.value.messenger,
supportMessengerSettings.value.messengerId,
)
})
),
)
const depositSmsSupportLink = computed(() =>
buildDepositSmsUrl(supportMessengerSettings.value.depositSmsSupportNumber),
@@ -132,7 +124,7 @@ export function useWalletPage() {
})
const disableButton = computed(() => {
const minWithdrawAmount = isBoursecuProject() ? 200_000 : 1_000_000
const minWithdrawAmount = 200_000
const minDepositAmount = 1_000_000
if (!loading.value) {
@@ -297,7 +289,6 @@ export function useWalletPage() {
depositGuideSupportLink,
depositSmsSupportLink,
depositQuickAmounts,
isBazretalaProject,
usePriceWithoutType,
formatCashWalletBalanceText,
setIsDeposit,
@@ -55,9 +55,6 @@
<Transition mode="out-in" name="fade">
<div v-if="!isDeposit" key="withdraw" class="dwallet-form__extras">
<p v-if="!isBoursecu" class="dwallet-form__hint dwallet-form__hint--warn">
حداقل مبلغ برداشتی 100 هزار تومان می باشد.
</p>
<p class="dwallet-form__balance-text">
{{ formatCashWalletBalanceText(usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0)) }}
</p>
@@ -81,7 +78,7 @@
</a-form-item>
</div>
<div v-else key="deposit" class="dwallet-form__extras">
<p v-if="!isBazretalaProject()" class="dwallet-form__hint dwallet-form__hint--warn">
<p class="dwallet-form__hint dwallet-form__hint--warn">
حداقل مبلغ واریزی 100 هزار تومان می باشد.
</p>
<div class="dwallet-quick-amounts">
@@ -108,7 +105,7 @@
<span v-else>{{ isDeposit ? 'واریز' : 'برداشت' }}</span>
</BasicButton>
<BoursecuWithdrawGuide v-if="!isDeposit && isBoursecu" />
<BoursecuWithdrawGuide v-if="!isDeposit" />
</a-form>
</section>
@@ -136,53 +133,34 @@
<template v-else>
<div class="dwallet-side__header">
<h3 class="dwallet-side__title">{{ isBoursecu ? 'حساب‌های بانکی' : 'کارت‌های بانکی' }}</h3>
<h3 class="dwallet-side__title">حساب‌های بانکی</h3>
<button
v-if="isBoursecu"
type="button"
class="dwallet-side__add-link"
@click="showAddCard = true"
>
+ افزودن شبا
</button>
<button
v-else
type="button"
class="dwallet-side__add-btn"
@click="showAddCard = true"
>
<BasicIcon icon="vuesax-linear:add" size="16" />
<span>افزودن کارت</span>
</button>
</div>
<p v-if="isBoursecu && cardItems.length > 0" class="dwallet-side__subtitle">واریز به</p>
<p v-if="cardItems.length > 0" class="dwallet-side__subtitle">واریز به</p>
<div v-if="!loading && cardItems.length === 0" class="dwallet-side__empty dwallet-side__empty--cards">
<BasicIcon icon="vuesax-linear:card" size="32" />
<p>هیچ کارتی یافت نشد</p>
<button type="button" class="dwallet-side__add-btn dwallet-side__add-btn--primary" @click="showAddCard = true">
{{ isBoursecu ? 'افزودن شبا' : 'افزودن کارت جدید' }}
افزودن شبا
</button>
</div>
<BoursecuBankAccountPicker
v-else-if="isBoursecu"
v-else
v-model:id="form.card_id"
variant="desktop"
:items="cardItems"
@add-account="showAddCard = true"
@card-deleted="getCards"
/>
<div v-else-if="cardItems.length > 0" class="dwallet-cards">
<CardSlider
v-model:id="form.card_id"
:items="cardItems"
class="dwallet-cards__slider"
@cardDeleted="getCards"
/>
</div>
</template>
</section>
</div>
@@ -199,15 +177,11 @@ import ListAnim from '@/components/ListAnim.vue'
import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import { useConfigStore } from '@/store/configStore'
import { required } from '@/utils/inputRules'
import CardSlider from '../components/UISlider/index.vue'
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
import BoursecuWithdrawGuide from '../components/BoursecuWithdrawGuide.vue'
import Item from '../components/Item.vue'
import { isBoursecuProject } from '@/utils/config'
import { useWalletPageContext } from '../composables/useWalletPage'
const isBoursecu = isBoursecuProject()
const {
isDeposit,
walletStore,
@@ -220,7 +194,6 @@ const {
loadingMore,
desktopDepositScrollEl,
depositQuickAmounts,
isBazretalaProject,
usePriceWithoutType,
formatCashWalletBalanceText,
setIsDeposit,
+1 -18
View File
@@ -83,22 +83,7 @@
:support-link="depositGuideSupportLink"
:sms-link="depositSmsSupportLink"
>
<template v-if="config.config.name === 'garoosi'">
<p>جهت شارژ حساب کاربری خود، مبلغ موردنظر را به شماره کارت زیر واریز نموده و از طریق لینک زیر به پشتیبان اطلاع دهید.</p>
<p class="deposit-guide-note deposit-guide-note--warn">در صورت عدم همخوانی کارت ملی و فیش واریزی کیف پول شارژ نمیشود!</p>
<p class="deposit-guide-note">(سیده زهرا موسوی)</p>
<p class="deposit-guide-note">شماره کارت: 6037701900311389</p>
<p class="deposit-guide-note">شماره شبا: IR 080160000000001143804581</p>
</template>
<template v-else-if="isBazretalaProject()">
<p>جهت شارژ حساب کاربری خود، مبلغ موردنظر را به شماره کارت زیر واریز نموده و به شماره پشتیبانی 09121784258 در برنامه بله اطلاع دهید.</p>
<p class="deposit-guide-note">(گنجينه طلاي حاجي ابوالفتح - بانک ملی)</p>
<p class="deposit-guide-note">شماره کارت: 6037998800496928</p>
<p class="deposit-guide-note">شماره شبا: IR 110170000000372468150002</p>
</template>
<template v-else>
<template>
<p>{{ supportMessengerText }}</p>
</template>
</WalletDepositGuideModal>
@@ -138,7 +123,6 @@ import WalletDepositGuideModal from './components/WalletDepositGuideModal.vue'
const isDesktop = useDesktopBreakpoint()
const {
config,
showAddCard,
chargeAccountModal,
showConfirmCode,
@@ -149,7 +133,6 @@ const {
supportMessengerText,
depositGuideSupportLink,
depositSmsSupportLink,
isBazretalaProject,
handleOnComplete,
loading,
addCard,
@@ -30,11 +30,11 @@
</div>
<div
class="px-5 mpage-content mpage-content--with-subheader"
:class="isDeposit ? 'pb-2' : isBoursecu ? 'pb-[calc(var(--app-bottom-chrome-height)+1rem)]' : 'pb-[calc(var(--app-bottom-chrome-height)+var(--app-add-card-height)+1rem)]'"
:class="isDeposit ? 'pb-2' : 'pb-[calc(var(--app-bottom-chrome-height)+1rem)]'"
>
<a-form :model="form" class="!mt-5" name="basic" @finish="ok()">
<BoursecuBankAccountPicker
v-if="!isDeposit && isBoursecu"
v-if="!isDeposit"
v-model:id="form.card_id"
variant="mobile"
:items="cardItems"
@@ -59,9 +59,6 @@
</a-form-item>
<Transition mode="out-in" name="fade">
<div v-if="!isDeposit">
<div v-if="!isBoursecu" class="text-red text-xs font-bold mb-2">
حداقل مبلغ برداشتی 100 هزار تومان می باشد.
</div>
<div class="font-bold text-xs">
{{ formatCashWalletBalanceText(usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0)) }}
</div>
@@ -85,7 +82,7 @@
</a-form-item>
</div>
<div v-else class="mt-3">
<div v-if="!isBazretalaProject()" class="text-red text-xs font-bold mb-2">
<div class="text-red text-xs font-bold mb-2">
حداقل مبلغ واریزی 100 هزار تومان می باشد.
</div>
<div class="flex gap-2">
@@ -118,7 +115,7 @@
</a-form>
<div
v-if="!isDeposit && isBoursecu"
v-if="!isDeposit"
class="wallet-withdraw-guide-panel relative z-10"
>
<BoursecuWithdrawGuide />
@@ -137,44 +134,12 @@
</list-anim>
<TransactionSkeleton v-for="i in 13" v-else :key="i" class="mt-1" />
</div>
<Transition mode="out-in" name="fade">
<template v-if="!isDeposit">
<div
v-if="!loading && cardItems.length === 0 && !isBoursecu"
class="h-30 flex items-center justify-center"
>
هیچ کارتی یافت نشد
</div>
</template>
</Transition>
</div>
<Teleport to="body">
<CardSlider
v-if="!isDeposit && !isBoursecu && cardItems.length > 0 && !showAddCard"
v-model:id="form.card_id"
:items="cardItems"
class="card-slider-wrap wallet-card-slider"
@cardDeleted="getCards"
/>
</Teleport>
<Teleport to="body">
<div
v-if="!isDeposit && !isBoursecu && !showAddCard"
class="add-card app-dock-add-bar"
@click="showAddCard = true"
>
<div>افزودن کارت جدید</div>
<BasicIcon icon="vuesax-linear:add" />
</div>
</Teleport>
</div>
</template>
<script lang="ts" setup>
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
import Bg_main from '@/components/bg_main.vue'
@@ -183,15 +148,11 @@ import TransactionSkeleton from '@/components/TransactionSkeleton.vue'
import { useConfigStore } from '@/store/configStore'
import { required } from '@/utils/inputRules'
import img from '@/assets/img/user_ly1.svg'
import CardSlider from '../components/UISlider/index.vue'
import BoursecuBankAccountPicker from '../components/BoursecuBankAccountPicker.vue'
import BoursecuWithdrawGuide from '../components/BoursecuWithdrawGuide.vue'
import Item from '../components/Item.vue'
import { isBoursecuProject } from '@/utils/config'
import { useWalletPageContext } from '../composables/useWalletPage'
const isBoursecu = isBoursecuProject()
const {
isDeposit,
walletStore,
@@ -204,7 +165,6 @@ const {
loadingMore,
mobileDepositScrollEl,
depositQuickAmounts,
isBazretalaProject,
usePriceWithoutType,
formatCashWalletBalanceText,
setIsDeposit,
+3 -51
View File
@@ -23,10 +23,7 @@
:src="getProjectLoginLogo(config.config?.avatar)"
alt=""
:class="[
'z-100 self-center w-36',
config.config?.name === 'garoosi'
? 'h-auto object-contain'
: 'rounded aspect-square',
'z-100 self-center w-36 rounded aspect-square',
]"
style="margin-bottom: 60px"
>
@@ -190,27 +187,7 @@
ورود با اثر انگشت
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<div v-if="!isTWA()" class="dauth-actions-row">
<a
class="dauth-link-btn dauth-link-btn--primary"
download="Garoosi.apk"
href="https://garoosigholaktala.ir/apk/Garoosi.apk"
>
دانلود اپ اندروید
</a>
<button type="button" class="dauth-link-btn" @click="installWebApp">
نصب نسخه وب اپ
</button>
</div>
<button type="button" class="dauth-link-btn" @click="goToEducate">
آموزش کار با نرم افزار
</button>
<button type="button" class="dauth-link-btn" @click="goToAboutUs">
درباره ما
</button>
</template>
<div v-else-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class="mt-1">
<div v-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class="mt-1">
<a
class="dauth-link-btn dauth-link-btn--primary !hover:text-primary "
:href="setting.getSettingByKey('apk_download_link')"
@@ -254,29 +231,7 @@
</div>
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<div v-if="!isTWA()" class="flex w-full gap-2 mt-2">
<a
class="flex w-full items-center justify-center rounded py-2 text-white bg-primary border-none cursor-pointer no-underline"
download="Garoosi.apk"
href="https://garoosigholaktala.ir/apk/Garoosi.apk"
>
<div>دانلود اپ اندروید</div>
</a>
<BasicButton type="primary" class="w-full" @click="installWebApp">
<div>نصب نسخه وب اپ</div>
</BasicButton>
</div>
<BasicButton class="w-full !font-bold mt-2" @click="goToEducate">
<div class="flex gap-2 items-center">آموزش کار با نرم افزار</div>
</BasicButton>
<BasicButton class="mt-2 w-full" icon="" iconSize="18" @click="goToAboutUs">
<div class="flex">
<div>درباره ما</div>
</div>
</BasicButton>
</template>
<div v-else-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class=" mt-1">
<div v-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class=" mt-1">
<a
class="dauth-link-btn dauth-link-btn--primary !hover:text-primary "
:href="setting.getSettingByKey('apk_download_link')"
@@ -294,9 +249,6 @@
ثبت نام کنید.
</div>
</a-form-item>
<!-- <BasicButton v-if="config.config.name === 'garoosi'" class="w-full !font-bold" @click="goToEducate">
<div class="flex gap-2 items-center">آموزش کار با نرم افزار</div>
</BasicButton>-->
</a-form>
<!-- <div v-if="URLUserType() === 'single'" class="fixed bottom-5 left-0 right-0 text-center">
+1 -24
View File
@@ -22,12 +22,7 @@
v-else-if="variant !== 'desktop'"
:src="getProjectLoginLogo(config.config?.avatar)"
alt=""
:class="[
'z-100 self-center w-36',
config.config?.name === 'garoosi'
? 'h-auto object-contain mb-[60px]'
: 'rounded aspect-square',
]"
class="z-100 self-center w-36 rounded aspect-square"
>
<div v-if="variant !== 'desktop'" class="font-bold text-center text-xl">ثبت نام</div>
<a-form
@@ -104,14 +99,6 @@
<a-spin v-if="loading" size="small" />
<span v-else>ثبت نام</span>
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<button type="button" class="dauth-link-btn" @click="goToEducate">
آموزش کار با نرم افزار
</button>
<button type="button" class="dauth-link-btn" @click="goToAboutUs">
درباره ما
</button>
</template>
</div>
<div v-if="variant === 'desktop'" class="dauth-switch">
@@ -133,16 +120,6 @@
<div v-else>ثبت نام</div>
</div>
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<BasicButton class="w-full mt-2 !font-bold" @click="goToEducate">
<div class="flex gap-2 items-center">آموزش کار با نرم افزار</div>
</BasicButton>
<BasicButton class="mt-2 w-full" icon="" iconSize="18" @click="goToAboutUs">
<div class="flex">
<div>درباره ما</div>
</div>
</BasicButton>
</template>
<div
class="cursor-pointer flex gap-1 justify-center mt-5 text-xs"
@click="$emit('register', false)"
@@ -1,20 +1,13 @@
<template>
<div class="dauth" :class="{ 'dauth--boursecu': isBoursecu }">
<div class="dauth dauth--boursecu">
<div class="dauth__layout">
<aside
class="dauth-brand"
:class="{
'dauth-brand--boursecu': isBoursecu,
'dauth-brand--soroushgold': isSoroushgold,
}"
>
<aside class="dauth-brand dauth-brand--boursecu">
<div class="dauth-brand__pattern" />
<div class="dauth-brand__content">
<img
:src="logo"
alt=""
class="dauth-brand__logo"
:class="{ 'dauth-brand__logo--round': !hasCustomLayout }"
>
<h1 class="dauth-brand__title">{{ brandTitle }}</h1>
<p v-if="tagline" class="dauth-brand__tagline">{{ tagline }}</p>
@@ -46,15 +39,9 @@ import { useConfigStore } from '@/store/configStore'
import {
getProjectDesktopLoginBrandLogo,
getProjectLoginTaglineLines,
hasCustomLoginLogoLayout,
isBoursecuProject,
isSoroushgoldProject,
} from '@/utils/config'
const config = useConfigStore()
const isBoursecu = isBoursecuProject()
const isSoroushgold = isSoroushgoldProject()
const hasCustomLayout = hasCustomLoginLogoLayout() || isSoroushgold
const version = getDisplayAppVersion(pkg.version)
const logo = computed(() => getProjectDesktopLoginBrandLogo(config.config?.avatar))
@@ -69,20 +56,11 @@ const tagline = computed(() => {
const brandTitle = computed(() => config.config?.name_fa ?? 'خوش آمدید')
const features = computed(() => {
if (isBoursecu) {
return [
'قیمت لحظه‌ای و نمودار بازار',
'مدیریت دارایی و کیف پول',
'معاملات سریع و امن',
]
}
return [
'ورود امن با پیامک یا رمز عبور',
'مدیریت یکپارچه دارایی‌ها',
'دسترسی سریع به خدمات',
]
})
const features = [
'قیمت لحظه‌ای و نمودار بازار',
'مدیریت دارایی و کیف پول',
'معاملات سریع و امن',
]
</script>
<style lang="less" src="../styles/desktop-auth.less"></style>
@@ -173,27 +173,7 @@ function goToAboutUs() {
ورود با اثر انگشت
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<div v-if="!isTWA()" class="dauth-actions-row">
<a
class="dauth-link-btn dauth-link-btn--primary"
download="Garoosi.apk"
href="https://garoosigholaktala.ir/apk/Garoosi.apk"
>
دانلود اپ اندروید
</a>
<button type="button" class="dauth-link-btn" @click="installWebApp">
نصب نسخه وب اپ
</button>
</div>
<button type="button" class="dauth-link-btn" @click="goToEducate">
آموزش کار با نرم افزار
</button>
<button type="button" class="dauth-link-btn" @click="goToAboutUs">
درباره ما
</button>
</template>
<div v-else-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class="dauth-actions-row ">
<div v-if="!isTWA() && !!setting.getSettingByKey('apk_download_link')" class="dauth-actions-row ">
<a
class="dauth-link-btn dauth-link-btn--primary !hover:text-primary "
:href="setting.getSettingByKey('apk_download_link')"
@@ -114,15 +114,6 @@ function handleSuccessDismiss() {
<a-spin v-if="loading" size="small" />
<span v-else>ثبت نام</span>
</BasicButton>
<template v-if="config.config?.name === 'garoosi'">
<button type="button" class="dauth-link-btn" @click="goToEducate">
آموزش کار با نرم افزار
</button>
<button type="button" class="dauth-link-btn" @click="goToAboutUs">
درباره ما
</button>
</template>
</div>
<div class="dauth-switch">
-47
View File
@@ -21,58 +21,23 @@
<!-- Mobile: original layout -->
<div v-show="!isDesktop" class="login-page flex flex-col items-center">
<template v-if="!isBoursecu">
<div class="fixed top-0 right-0 left-0 h-1/3 w-screen flex">
<img :src="img_topGradiant" alt="" class="absolute z-10 w-screen h-full object-cover" />
<img :src="img_pattern" alt="" class="absolute w-screen h-full object-none" />
</div>
<div class="fixed bottom-0 right-0 left-0 h-1/3 w-screen">
<img :src="img_bottomGradiant" alt="" class="absolute z-10 w-screen h-full object-cover" />
<img :src="img_pattern" alt="" class="absolute w-screen h-full object-none" />
</div>
</template>
<div class="login-page__content z-1000 w-full p-5">
<Login v-if="!loginPage.enterType" @register="loginPage.setEnterType" />
<Register v-else @register="loginPage.setEnterType" />
</div>
<div v-if="isGaroosi" class="login-page__enamad z-1000">
<a
referrerpolicy="origin"
target="_blank"
href="https://trustseal.enamad.ir/?id=710578&Code=DdltBYWJgvc7LJs9eM46SEliXRRU0kSW"
>
<img
referrerpolicy="origin"
src="https://trustseal.enamad.ir/logo.aspx?id=710578&Code=DdltBYWJgvc7LJs9eM46SEliXRRU0kSW"
alt="enamad"
style="cursor: pointer"
code="DdltBYWJgvc7LJs9eM46SEliXRRU0kSW"
>
</a>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useLoginPage } from './composables/useLoginPage'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { isBoursecuProject } from '@/utils/config'
import { useConfigStore } from '@/store/configStore'
import Login from './components/Login.vue'
import Register from './components/Register.vue'
import DesktopAuthShell from './desktop/components/DesktopAuthShell.vue'
import img_topGradiant from './assets/img/gr_top_white.svg'
import img_bottomGradiant from './assets/img/gr_bottom_white.svg'
import img_pattern from './assets/img/pattern.svg'
const isDesktop = useDesktopBreakpoint()
const isBoursecu = isBoursecuProject()
const loginPage = useLoginPage()
const config = useConfigStore()
const isGaroosi = computed(() => config.config?.name === 'garoosi')
</script>
<style scoped>
@@ -98,16 +63,4 @@ const isGaroosi = computed(() => config.config?.name === 'garoosi')
align-items: center;
justify-content: center;
}
.login-page__enamad {
display: flex;
justify-content: center;
padding: 0 20px 16px;
margin-top: auto;
}
.login-page__enamad img {
max-width: 96px;
height: auto;
}
</style>
@@ -1,15 +1,5 @@
<template>
<div class="login-page flex items-center justify-center">
<template v-if="!isBoursecu">
<div class="fixed top-0 right-0 left-0 h-1/3 w-screen flex">
<img :src="img_topGradiant" alt="" class="absolute z-10 w-screen h-full object-cover" />
<img :src="img_pattern" alt="" class="absolute w-screen h-full object-none" />
</div>
<div class="fixed bottom-0 right-0 left-0 h-1/3 w-screen">
<img :src="img_bottomGradiant" alt="" class="absolute z-10 w-screen h-full object-cover" />
<img :src="img_pattern" alt="" class="absolute w-screen h-full object-none" />
</div>
</template>
<div class="z-1000 w-full p-5">
<Login v-if="!page.enterType" key="login" @register="page.setEnterType" />
<Register v-else key="register" @register="page.setEnterType" />
@@ -21,13 +11,8 @@
import Login from '../components/Login.vue'
import Register from '../components/Register.vue'
import { useLoginPageContext } from '../composables/useLoginPage'
import { isBoursecuProject } from '@/utils/config'
import img_topGradiant from '../assets/img/gr_top_white.svg'
import img_bottomGradiant from '../assets/img/gr_bottom_white.svg'
import img_pattern from '../assets/img/pattern.svg'
const page = useLoginPageContext()
const isBoursecu = isBoursecuProject()
</script>
<style scoped>