fix pargar mande
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import useAxios from '@/utils/useAxios'
|
||||
|
||||
export type AppNotificationSeverity = 'info' | 'success' | 'warning' | 'danger'
|
||||
|
||||
export interface CustomerAppNotification {
|
||||
id: number
|
||||
title: string
|
||||
body: string
|
||||
severity: AppNotificationSeverity
|
||||
created_at?: string
|
||||
is_read: boolean
|
||||
}
|
||||
|
||||
export const getAppNotifications = async () =>
|
||||
await useAxios.get<CustomerAppNotification[]>(`/customer/app_notifications`)
|
||||
|
||||
export const getAppNotificationsUnreadCount = async () =>
|
||||
await useAxios.get<{ unread_count: number }>(`/customer/app_notifications/unread-count`)
|
||||
|
||||
export const markAppNotificationRead = async (id: number) =>
|
||||
await useAxios.post(`/customer/app_notifications/${id}/read`)
|
||||
|
||||
export const markAllAppNotificationsRead = async () =>
|
||||
await useAxios.post(`/customer/app_notifications/read-all`)
|
||||
@@ -55,6 +55,28 @@ export function setupRouterGuards() {
|
||||
clearFingerTimeout()
|
||||
setFingerTimeOut(to)
|
||||
|
||||
if (to.name === 'notifyTrigger' && getToken()) {
|
||||
const { useSettingsStore } = await import('@/store/settingsStore')
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!settingsStore.loaded) {
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
if (!settingsStore.isSettingTruthy('has_page_price_trigger')) {
|
||||
return next({ name: (from.name as string) || 'user' })
|
||||
}
|
||||
}
|
||||
|
||||
if (to.name === 'appNotifications' && getToken()) {
|
||||
const { useSettingsStore } = await import('@/store/settingsStore')
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!settingsStore.loaded) {
|
||||
await settingsStore.fetchSettings()
|
||||
}
|
||||
if (!settingsStore.isSettingTruthy('has_page_notification')) {
|
||||
return next({ name: (from.name as string) || 'home' })
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirst) {
|
||||
isFirst = false
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { getAppNotificationsUnreadCount } from '@/api/appNotifications'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { isToken } from '@/utils'
|
||||
|
||||
const unreadCount = ref(0)
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let started = false
|
||||
|
||||
async function refreshUnreadCount() {
|
||||
const settingsStore = useSettingsStore()
|
||||
if (!isToken() || !settingsStore.isSettingTruthy('has_page_notification')) {
|
||||
unreadCount.value = 0
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await getAppNotificationsUnreadCount()
|
||||
unreadCount.value = Number(data?.unread_count ?? 0) || 0
|
||||
} catch {
|
||||
// silent — badge optional
|
||||
}
|
||||
}
|
||||
|
||||
function clearUnreadLocally() {
|
||||
unreadCount.value = 0
|
||||
}
|
||||
|
||||
export function useAppNotificationBadge() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const enabled = computed(() => settingsStore.isSettingTruthy('has_page_notification'))
|
||||
|
||||
onMounted(() => {
|
||||
if (started) {
|
||||
void refreshUnreadCount()
|
||||
return
|
||||
}
|
||||
started = true
|
||||
void refreshUnreadCount()
|
||||
pollTimer = setInterval(() => {
|
||||
void refreshUnreadCount()
|
||||
}, 60000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// keep poll for other mounted headers; only clear if needed later
|
||||
})
|
||||
|
||||
watch(enabled, (on) => {
|
||||
if (!on) unreadCount.value = 0
|
||||
else void refreshUnreadCount()
|
||||
})
|
||||
|
||||
return {
|
||||
enabled,
|
||||
unreadCount,
|
||||
refreshUnreadCount,
|
||||
clearUnreadLocally,
|
||||
}
|
||||
}
|
||||
|
||||
export { refreshUnreadCount, clearUnreadLocally }
|
||||
@@ -43,6 +43,7 @@ import logoFull from '@/assets/img/logo_full_.png'
|
||||
import { isToken } from '@/utils'
|
||||
import { useCartStore } from '@/store/cartStore'
|
||||
import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { getPrimaryNavItems, getSecondaryNavItems } from '../config/navigation'
|
||||
import { useAppNavigation } from '../composables/useAppNavigation'
|
||||
import DesktopNavItem from './DesktopNavItem.vue'
|
||||
@@ -51,13 +52,20 @@ const cartStore = useCartStore()
|
||||
const authenticated = computed(() => isToken())
|
||||
const navLogo = logoFull
|
||||
const { trackingSystemsEnabled } = useTrackingSystemsFeature()
|
||||
const settingsStore = useSettingsStore()
|
||||
const primaryItems = computed(() => getPrimaryNavItems(authenticated.value))
|
||||
/** «لیست دریافتیها» فقط در موبایل نمایش داده میشود */
|
||||
const DESKTOP_HIDDEN_NAV_IDS = ['list']
|
||||
const secondaryItems = computed(() =>
|
||||
getSecondaryNavItems(authenticated.value, {
|
||||
includeRecoverySites: trackingSystemsEnabled.value,
|
||||
}).filter((item) => !DESKTOP_HIDDEN_NAV_IDS.includes(item.id)),
|
||||
}).filter((item) => {
|
||||
if (DESKTOP_HIDDEN_NAV_IDS.includes(item.id)) return false
|
||||
if (item.id === 'notifyTrigger') {
|
||||
return settingsStore.isSettingTruthy('has_page_price_trigger')
|
||||
}
|
||||
return true
|
||||
}),
|
||||
)
|
||||
const cartCount = computed(() => cartStore.itemCount || 0)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</BasicButton>
|
||||
<BasicButton
|
||||
v-if="authenticated"
|
||||
v-if="authenticated && priceTriggerEnabled"
|
||||
icon-size="16"
|
||||
class="!font-bold w-15rem"
|
||||
@click="go('notifyTrigger')"
|
||||
@@ -120,6 +120,7 @@ import { computed } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { isToken } from '@/utils'
|
||||
|
||||
defineProps<{
|
||||
@@ -133,6 +134,10 @@ const emit = defineEmits<{
|
||||
|
||||
const authenticated = computed(() => isToken())
|
||||
const { trackingSystemsEnabled } = useTrackingSystemsFeature()
|
||||
const settingsStore = useSettingsStore()
|
||||
const priceTriggerEnabled = computed(() =>
|
||||
settingsStore.isSettingTruthy('has_page_price_trigger'),
|
||||
)
|
||||
|
||||
function go(routeName: string) {
|
||||
emit('navigate', routeName)
|
||||
|
||||
@@ -75,6 +75,11 @@ const router = createRouter({
|
||||
name: 'notifyTrigger',
|
||||
component: () => import('@/views/base/priceTrigger/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'appNotifications',
|
||||
name: 'appNotifications',
|
||||
component: () => import('@/views/base/appNotifications/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'transfer',
|
||||
name: 'transfer',
|
||||
|
||||
@@ -24,6 +24,18 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
return item?.value ?? null
|
||||
}
|
||||
|
||||
function isSettingTruthy(key: string) {
|
||||
const value = getSettingByKey(key)
|
||||
if (value == null) {
|
||||
return false
|
||||
}
|
||||
if (value === true) {
|
||||
return true
|
||||
}
|
||||
const normalized = String(value).trim().toLowerCase()
|
||||
return normalized === 'true' || normalized === '1' || normalized === 'yes'
|
||||
}
|
||||
|
||||
function mergeSettings(items?: AppSetting[] | null) {
|
||||
if (!items?.length) {
|
||||
return
|
||||
@@ -80,6 +92,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
fetchSettings,
|
||||
mergeSettings,
|
||||
getSettingByKey,
|
||||
isSettingTruthy,
|
||||
metalTypeSettingValue,
|
||||
metalTypeLabel,
|
||||
meltedGoldLabel,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { computed, inject, onMounted, provide, ref, type InjectionKey } from 'vue'
|
||||
import {
|
||||
getAppNotifications,
|
||||
markAllAppNotificationsRead,
|
||||
markAppNotificationRead,
|
||||
type CustomerAppNotification,
|
||||
} from '@/api/appNotifications'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
export interface AppNotificationsPageContext {
|
||||
loading: ReturnType<typeof ref<boolean>>
|
||||
items: ReturnType<typeof ref<CustomerAppNotification[]>>
|
||||
enabled: ReturnType<typeof computed<boolean>>
|
||||
refresh: () => Promise<void>
|
||||
markRead: (id: number) => Promise<void>
|
||||
markAllRead: () => Promise<void>
|
||||
}
|
||||
|
||||
export const APP_NOTIFICATIONS_PAGE_KEY: InjectionKey<AppNotificationsPageContext> =
|
||||
Symbol('appNotificationsPage')
|
||||
|
||||
export function useAppNotificationsPage() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const loading = ref(false)
|
||||
const items = ref<CustomerAppNotification[]>([])
|
||||
|
||||
const enabled = computed(() => settingsStore.isSettingTruthy('has_page_notification'))
|
||||
|
||||
async function refresh() {
|
||||
if (!enabled.value) {
|
||||
items.value = []
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await getAppNotifications()
|
||||
items.value = Array.isArray(data) ? data : []
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
items.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function markRead(id: number) {
|
||||
try {
|
||||
await markAppNotificationRead(id)
|
||||
const row = items.value.find((item) => item.id === id)
|
||||
if (row) row.is_read = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function markAllRead() {
|
||||
try {
|
||||
await markAllAppNotificationsRead()
|
||||
items.value = items.value.map((item) => ({ ...item, is_read: true }))
|
||||
const { clearUnreadLocally } = await import('@/composables/useAppNotificationBadge')
|
||||
clearUnreadLocally()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void refresh().then(() => {
|
||||
const hasUnread = items.value.some((item) => !item.is_read)
|
||||
if (hasUnread) {
|
||||
void markAllRead()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const ctx: AppNotificationsPageContext = {
|
||||
loading,
|
||||
items,
|
||||
enabled,
|
||||
refresh,
|
||||
markRead,
|
||||
markAllRead,
|
||||
}
|
||||
provide(APP_NOTIFICATIONS_PAGE_KEY, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function useAppNotificationsPageContext() {
|
||||
const ctx = inject(APP_NOTIFICATIONS_PAGE_KEY)
|
||||
if (!ctx) {
|
||||
throw new Error('useAppNotificationsPageContext must be used within useAppNotificationsPage')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="dapp-notif">
|
||||
<header class="dapp-notif__top">
|
||||
<div>
|
||||
<p class="dapp-notif__eyebrow">اعلانها</p>
|
||||
<h1 class="dapp-notif__title">پیامهای مدیریت</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="!items.length" class="dapp-notif__empty">
|
||||
<BasicIcon icon="vuesax-linear:notification-bing" size="36" />
|
||||
<p>اعلانی برای نمایش نیست.</p>
|
||||
</div>
|
||||
<div v-else class="dapp-notif__list">
|
||||
<article
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="dapp-notif__card"
|
||||
:class="`dapp-notif__card--${item.severity}`"
|
||||
@click="markRead(item.id)"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<h3 class="m-0 text-base font-bold">{{ item.title }}</h3>
|
||||
<span class="dapp-notif__tag">{{ severityLabel(item.severity) }}</span>
|
||||
</div>
|
||||
<p class="m-0 mt-2 text-sm text-gray-600 whitespace-pre-wrap">{{ item.body }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useAppNotificationsPageContext } from '../composables/useAppNotificationsPage'
|
||||
|
||||
const { loading, items, markRead } = useAppNotificationsPageContext()
|
||||
|
||||
function severityLabel(severity: string) {
|
||||
switch (severity) {
|
||||
case 'success':
|
||||
return 'موفق'
|
||||
case 'warning':
|
||||
return 'هشدار'
|
||||
case 'danger':
|
||||
return 'مهم'
|
||||
default:
|
||||
return 'اطلاعات'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dapp-notif {
|
||||
@apply p-6 min-h-full;
|
||||
}
|
||||
.dapp-notif__eyebrow {
|
||||
@apply m-0 text-xs text-gray-500;
|
||||
}
|
||||
.dapp-notif__title {
|
||||
@apply m-0 mt-1 text-xl font-bold;
|
||||
}
|
||||
.dapp-notif__empty {
|
||||
@apply mt-10 flex flex-col items-center gap-2 text-gray-500;
|
||||
}
|
||||
.dapp-notif__list {
|
||||
@apply mt-6 flex flex-col gap-3 max-w-3xl;
|
||||
}
|
||||
.dapp-notif__card {
|
||||
@apply rounded-xl bg-white border border-gray-100 p-4 cursor-pointer;
|
||||
}
|
||||
.dapp-notif__tag {
|
||||
@apply shrink-0 rounded-md px-2 py-0.5 text-xs font-bold bg-gray-50 text-gray-700;
|
||||
}
|
||||
.dapp-notif__card--info .dapp-notif__tag {
|
||||
@apply bg-blue-50 text-blue-700;
|
||||
}
|
||||
.dapp-notif__card--success .dapp-notif__tag {
|
||||
@apply bg-green-50 text-green-700;
|
||||
}
|
||||
.dapp-notif__card--warning .dapp-notif__tag {
|
||||
@apply bg-orange-50 text-orange-700;
|
||||
}
|
||||
.dapp-notif__card--danger .dapp-notif__tag {
|
||||
@apply bg-red-50 text-red-700;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="app-notifications-page-root">
|
||||
<DesktopAppNotificationsView v-show="isDesktop" />
|
||||
<MobileAppNotificationsView v-show="!isDesktop" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import { useAppNotificationsPage } from './composables/useAppNotificationsPage'
|
||||
import DesktopAppNotificationsView from './desktop/DesktopAppNotificationsView.vue'
|
||||
import MobileAppNotificationsView from './mobile/MobileAppNotificationsView.vue'
|
||||
|
||||
const isDesktop = useDesktopBreakpoint()
|
||||
useAppNotificationsPage()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-notifications-page-root {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="app-notif-page">
|
||||
<MobilePageHeader title="اعلانها" show-back />
|
||||
<div class="app-notif-page__body">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="!items.length" class="app-notif-page__empty">
|
||||
<BasicIcon icon="vuesax-linear:notification-bing" size="40" class="text-gray-400" />
|
||||
<p class="m-0 mt-3 font-bold text-gray-700">اعلانی نیست</p>
|
||||
<p class="m-0 mt-1 text-sm text-gray-500">هنوز اعلانی از طرف مدیریت ارسال نشده است.</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-3">
|
||||
<article
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="app-notif-card"
|
||||
:class="[
|
||||
`app-notif-card--${item.severity}`,
|
||||
{ 'app-notif-card--unread': !item.is_read },
|
||||
]"
|
||||
@click="markRead(item.id)"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<h3 class="m-0 text-sm font-bold text-gray-900">{{ item.title }}</h3>
|
||||
<span class="app-notif-card__badge">{{ severityLabel(item.severity) }}</span>
|
||||
</div>
|
||||
<p class="m-0 mt-2 text-sm text-gray-600 whitespace-pre-wrap">{{ item.body }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import { useAppNotificationsPageContext } from '../composables/useAppNotificationsPage'
|
||||
|
||||
const { loading, items, markRead } = useAppNotificationsPageContext()
|
||||
|
||||
function severityLabel(severity: string) {
|
||||
switch (severity) {
|
||||
case 'success':
|
||||
return 'موفق'
|
||||
case 'warning':
|
||||
return 'هشدار'
|
||||
case 'danger':
|
||||
return 'مهم'
|
||||
default:
|
||||
return 'اطلاعات'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-notif-page {
|
||||
@apply min-h-full flex flex-col bg-gray-50;
|
||||
}
|
||||
.app-notif-page__body {
|
||||
@apply px-4 pb-8 pt-2;
|
||||
}
|
||||
.app-notif-page__empty {
|
||||
@apply flex flex-col items-center justify-center rounded-xl bg-white p-8 text-center;
|
||||
}
|
||||
.app-notif-card {
|
||||
@apply rounded-xl bg-white p-3 border border-gray-100;
|
||||
}
|
||||
.app-notif-card--unread {
|
||||
@apply border-gray-200 shadow-sm;
|
||||
}
|
||||
.app-notif-card__badge {
|
||||
@apply shrink-0 rounded-md px-2 py-0.5 text-xs font-bold;
|
||||
}
|
||||
.app-notif-card--info .app-notif-card__badge {
|
||||
@apply bg-blue-50 text-blue-700;
|
||||
}
|
||||
.app-notif-card--success .app-notif-card__badge {
|
||||
@apply bg-green-50 text-green-700;
|
||||
}
|
||||
.app-notif-card--warning .app-notif-card__badge {
|
||||
@apply bg-orange-50 text-orange-700;
|
||||
}
|
||||
.app-notif-card--danger .app-notif-card__badge {
|
||||
@apply bg-red-50 text-red-700;
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="w-full mr-3">
|
||||
<div class="flex justify-between w-full">
|
||||
<div>{{ item.title }}</div>
|
||||
<div>{{ toJalaliNotime(item.expire_at) }}</div>
|
||||
<div>{{ toJalali(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<div
|
||||
@@ -63,7 +63,7 @@ import { computed, ref } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { formatGoldAmount } from '@/utils/usePrice'
|
||||
import { toJalaliNotime } from '@/utils/useDateTime'
|
||||
import { toJalali } from '@/utils/useDateTime'
|
||||
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
|
||||
import TransactionDetailContent from './TransactionDetailContent.vue'
|
||||
|
||||
|
||||
@@ -27,11 +27,7 @@
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="text-black">تاریخ:</div>
|
||||
<div>{{ toJalaliNotime(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="text-black">زمان:</div>
|
||||
<div>{{ toTime(item.expire_at) }}</div>
|
||||
<div>{{ toJalali(item.expire_at) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,7 +35,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { formatGoldAmount } from '@/utils/usePrice'
|
||||
import { toJalaliNotime, toTime } from '@/utils/useDateTime'
|
||||
import { toJalali } from '@/utils/useDateTime'
|
||||
|
||||
defineProps<{
|
||||
item: Transaction
|
||||
|
||||
@@ -14,14 +14,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--logout"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<Icon icon="vuesax-linear:logout" size="18" />
|
||||
<span>خروج</span>
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="notificationEnabled"
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--notify"
|
||||
aria-label="اعلان"
|
||||
@click.stop="goNotifications"
|
||||
>
|
||||
<span class="mhome-header-btn__icon-wrap">
|
||||
<Icon icon="vuesax-linear:notification-bing" size="18" />
|
||||
<span v-if="unreadCount > 0" class="mhome-header-btn__badge">
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mhome-header-btn mhome-header-btn--logout"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<Icon icon="vuesax-linear:logout" size="18" />
|
||||
<span>خروج</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -30,6 +46,7 @@
|
||||
import { nextTick, onActivated, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useAppNotificationBadge } from '@/composables/useAppNotificationBadge'
|
||||
import router from '@/router'
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
|
||||
@@ -38,6 +55,16 @@ const isHeaderScrolled = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const {
|
||||
enabled: notificationEnabled,
|
||||
unreadCount,
|
||||
clearUnreadLocally,
|
||||
} = useAppNotificationBadge()
|
||||
|
||||
function goNotifications() {
|
||||
clearUnreadLocally()
|
||||
void router.push({ name: 'appNotifications' })
|
||||
}
|
||||
|
||||
let intersectionObserver: IntersectionObserver | null = null
|
||||
let scrollTarget: HTMLElement | Window | null = null
|
||||
@@ -205,4 +232,31 @@ defineEmits<{
|
||||
border-color: #fecaca;
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.mhome-header-btn--notify {
|
||||
color: #0a1b37;
|
||||
border-color: #e5e7eb;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.mhome-header-btn__icon-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.mhome-header-btn__badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 999px;
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
<BasicIcon icon="vuesax-linear:user" size="16" />
|
||||
<span>حساب کاربری</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="notificationEnabled"
|
||||
type="button"
|
||||
class="dhome-btn-ghost"
|
||||
aria-label="اعلان"
|
||||
@click="goNotifications"
|
||||
>
|
||||
<span class="dhome-notify-wrap">
|
||||
<BasicIcon icon="vuesax-linear:notification-bing" size="16" />
|
||||
<span v-if="unreadCount > 0" class="dhome-notify-badge">
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</span>
|
||||
</span>
|
||||
<span>اعلان</span>
|
||||
</button>
|
||||
<button type="button" class="dhome-btn-ghost" @click="$emit('logout')">
|
||||
<BasicIcon icon="vuesax-linear:logout" size="16" />
|
||||
<span>خروج</span>
|
||||
@@ -21,6 +36,7 @@
|
||||
<script lang="ts" setup>
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useAppNotificationBadge } from '@/composables/useAppNotificationBadge'
|
||||
import router from '@/router'
|
||||
|
||||
defineEmits<{
|
||||
@@ -28,4 +44,36 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const {
|
||||
enabled: notificationEnabled,
|
||||
unreadCount,
|
||||
clearUnreadLocally,
|
||||
} = useAppNotificationBadge()
|
||||
|
||||
function goNotifications() {
|
||||
clearUnreadLocally()
|
||||
void router.push({ name: 'appNotifications' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dhome-notify-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.dhome-notify-badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 999px;
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
<BasicIcon icon="vuesax-linear:user" size="16" />
|
||||
<span>حساب کاربری</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="notificationEnabled"
|
||||
type="button"
|
||||
class="dhome-btn-ghost"
|
||||
aria-label="اعلان"
|
||||
@click="goNotifications"
|
||||
>
|
||||
<span class="dhome-notify-wrap">
|
||||
<BasicIcon icon="vuesax-linear:notification-bing" size="16" />
|
||||
<span v-if="unreadCount > 0" class="dhome-notify-badge">
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</span>
|
||||
</span>
|
||||
<span>اعلان</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="showLogout"
|
||||
type="button"
|
||||
@@ -26,6 +41,7 @@
|
||||
<script lang="ts" setup>
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useAppNotificationBadge } from '@/composables/useAppNotificationBadge'
|
||||
import router from '@/router'
|
||||
|
||||
withDefaults(
|
||||
@@ -40,4 +56,36 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const {
|
||||
enabled: notificationEnabled,
|
||||
unreadCount,
|
||||
clearUnreadLocally,
|
||||
} = useAppNotificationBadge()
|
||||
|
||||
function goNotifications() {
|
||||
clearUnreadLocally()
|
||||
void router.push({ name: 'appNotifications' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dhome-notify-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.dhome-notify-badge {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 999px;
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -109,11 +109,7 @@
|
||||
</div>
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__row' : 'flex justify-between mb-3'">
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__label' : 'text-primary'">تاریخ:</div>
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__value' : ''">{{ toJalaliNotime(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__row' : 'flex justify-between mb-3'">
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__label' : 'text-primary'">زمان:</div>
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__value' : ''">{{ toTime(item.expire_at) }}</div>
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__value' : ''">{{ toJalali(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div v-if="item.eaj" :class="variant === 'desktop' ? 'dtxn-detail__row' : 'flex justify-between mb-3'">
|
||||
<div :class="variant === 'desktop' ? 'dtxn-detail__label' : 'text-primary'">مهلت ارسال فیش:</div>
|
||||
@@ -130,7 +126,7 @@ import {
|
||||
usePriceWithoutType,
|
||||
} from '@/utils/usePrice'
|
||||
import type { Transaction } from '@/models/transaction'
|
||||
import { toJalaliNotime, toTime } from '@/utils/useDateTime'
|
||||
import { toJalali } from '@/utils/useDateTime'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
||||
@@ -13,6 +13,7 @@ import { message } from 'ant-design-vue'
|
||||
import { updateMe, updatePass } from '@/api/me'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
export interface UserMenuAction {
|
||||
id: string
|
||||
@@ -62,6 +63,7 @@ export function useUserPage() {
|
||||
const editPassVisible = ref(false)
|
||||
const editPasswordVisible = ref(false)
|
||||
const { trackingSystemsEnabled } = useTrackingSystemsFeature()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const isLegal = computed(() => {
|
||||
const result = { title: '', color: '', icon: '' }
|
||||
@@ -97,7 +99,7 @@ export function useUserPage() {
|
||||
label: 'هشدار قیمت',
|
||||
icon: 'vuesax-linear:bill',
|
||||
onClick: () => router.push({ name: 'notifyTrigger' }),
|
||||
visible: true,
|
||||
visible: settingsStore.isSettingTruthy('has_page_price_trigger'),
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
</div>
|
||||
</BasicButton>-->
|
||||
<BasicButton
|
||||
v-if="priceTriggerEnabled"
|
||||
class="w-full mt-2"
|
||||
size="large"
|
||||
@click="$router.push({ name: 'notifyTrigger' })"
|
||||
@@ -226,8 +227,9 @@ import DialogVant from '@/components/DialogVant.vue'
|
||||
import { useWalletStore } from '@/store/walletStore'
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useWindowScroll } from '@vueuse/core/index'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, computed, ref } from 'vue'
|
||||
import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useUserPageContext } from '../composables/useUserPage'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
@@ -238,6 +240,10 @@ const showLogoutConfirm = ref<boolean>()
|
||||
const logoutLoading = ref<boolean>(false)
|
||||
const { y } = useWindowScroll()
|
||||
const { trackingSystemsEnabled } = useTrackingSystemsFeature()
|
||||
const settingsStore = useSettingsStore()
|
||||
const priceTriggerEnabled = computed(() =>
|
||||
settingsStore.isSettingTruthy('has_page_price_trigger'),
|
||||
)
|
||||
|
||||
const { editPassVisible, editPasswordVisible, copyCode, shareCode } = useUserPageContext()
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="w-full mr-3">
|
||||
<div class="flex justify-between w-full">
|
||||
<div>{{ item.type_str }}</div>
|
||||
<div>{{ toJalaliNotime(item.created_at) }}</div>
|
||||
<div>{{ toJalali(item.created_at) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<div :class="item.gold >= 0? 'text-green-600':'text-red-600'" class="text-gray-500 text-xs number-ltr">
|
||||
@@ -111,20 +111,8 @@
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="text-black">تاریخ:</div>
|
||||
<div>{{ toJalaliNotime(item.expire_at) }}</div>
|
||||
<div>{{ toJalali(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="text-black">زمان:</div>
|
||||
<div>{{ toTime(item.expire_at) }}</div>
|
||||
</div>
|
||||
<!-- <div class="flex justify-between mb-3">
|
||||
<div class="text-black">وضعیت معامله:</div>
|
||||
<div class="flex text-xs items-center">
|
||||
<ATag class="!py-1 !px-6 !ml-0" color="orange">
|
||||
معاملات باز
|
||||
</ATag>
|
||||
</div>
|
||||
</div>-->
|
||||
<div v-if="item.eaj" class="flex justify-between mb-3">
|
||||
<div class="text-black">مهلت ارسال فیش:</div>
|
||||
<div>{{ item.eaj }}</div>
|
||||
@@ -143,7 +131,7 @@ import BasicActionSheet from "@/components/BasicPacks/BasicActionSheet.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import BasicButton from "@/components/BasicPacks/BasicButton.vue";
|
||||
import type {Transaction} from "@/models/transaction";
|
||||
import {toJalaliNotime, toJalaliOutOfUTC, toTime} from "../../../../utils/useDateTime";
|
||||
import {toJalali, toJalaliOutOfUTC} from "../../../../utils/useDateTime";
|
||||
|
||||
const infoVisible = ref<boolean>(false)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user