Add web app install button and step-by-step PWA guide.

This commit is contained in:
2026-07-30 10:20:04 +02:00
parent e6dea15752
commit be896fe247
18 changed files with 1300 additions and 84 deletions
@@ -10,6 +10,15 @@
</div>
<div class="flex items-center gap-2">
<button
v-if="showInstallEntry"
type="button"
class="mhome-header-btn mhome-header-btn--install"
:aria-label="canNativeInstall ? 'نصب وب اپلیکیشن' : 'آموزش نصب'"
@click.stop="onInstall"
>
<Icon icon="vuesax-linear:mobile" size="18" />
</button>
<button
v-if="notificationEnabled"
type="button"
@@ -52,9 +61,16 @@ import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { getProjectMobileNavLogo } from '@/utils/config'
import { useAppNotificationBadge } from '@/composables/useAppNotificationBadge'
import { canRequestBrowserNotification, requestBrowserNotificationPermission } from '@/utils/browserNotification'
import { usePwaInstall } from '@/composables/usePwaInstall'
import { PWA_MANIFEST_READY_EVENT } from '@/modules/pwa/manifest'
import {
hasNativeInstallPrompt,
initPwaInstallPromptListener,
} from '@/modules/pwa/pwaInstall'
const headerRef = ref<HTMLElement | null>(null)
const isHeaderScrolled = ref(false)
const canNativeInstall = ref(false)
const route = useRoute()
const projectLogo = computed(() => getProjectMobileNavLogo())
@@ -63,6 +79,16 @@ const {
unreadCount,
clearUnreadLocally,
} = useAppNotificationBadge()
const { tryInstallOrOpenGuide, showInstallEntry } = usePwaInstall()
function refreshCanInstall() {
canNativeInstall.value = hasNativeInstallPrompt()
}
async function onInstall() {
await tryInstallOrOpenGuide()
refreshCanInstall()
}
async function goNotifications() {
clearUnreadLocally()
@@ -172,6 +198,10 @@ function setupScrollTracking() {
}
onMounted(async () => {
initPwaInstallPromptListener()
refreshCanInstall()
window.addEventListener(PWA_MANIFEST_READY_EVENT, refreshCanInstall)
window.addEventListener('appinstalled', refreshCanInstall)
setupScrollTracking()
await nextTick()
setupScrollTracking()
@@ -195,6 +225,8 @@ watch(
onUnmounted(() => {
teardownScrollTracking()
window.removeEventListener(PWA_MANIFEST_READY_EVENT, refreshCanInstall)
window.removeEventListener('appinstalled', refreshCanInstall)
})
defineEmits<{
@@ -10,6 +10,16 @@
<BasicIcon icon="vuesax-linear:user" size="16" />
<span>حساب کاربری</span>
</button>
<button
v-if="showInstallEntry"
type="button"
class="dhome-btn-ghost"
:aria-label="canNativeInstall ? 'نصب وب اپلیکیشن' : 'آموزش نصب'"
@click="onInstall"
>
<BasicIcon icon="vuesax-linear:mobile" size="16" />
<span>{{ canNativeInstall ? 'نصب وب اپ' : 'آموزش نصب' }}</span>
</button>
<button
v-if="notificationEnabled"
type="button"
@@ -42,10 +52,17 @@
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { useUserStore } from '@/store/userStore'
import { useAppNotificationBadge } from '@/composables/useAppNotificationBadge'
import { canRequestBrowserNotification, requestBrowserNotificationPermission } from '@/utils/browserNotification'
import { usePwaInstall } from '@/composables/usePwaInstall'
import { PWA_MANIFEST_READY_EVENT } from '@/modules/pwa/manifest'
import {
hasNativeInstallPrompt,
initPwaInstallPromptListener,
} from '@/modules/pwa/pwaInstall'
import router from '@/router'
withDefaults(
@@ -60,11 +77,22 @@ defineEmits<{
}>()
const userStore = useUserStore()
const canNativeInstall = ref(false)
const {
enabled: notificationEnabled,
unreadCount,
clearUnreadLocally,
} = useAppNotificationBadge()
const { tryInstallOrOpenGuide, showInstallEntry } = usePwaInstall()
function refreshCanInstall() {
canNativeInstall.value = hasNativeInstallPrompt()
}
async function onInstall() {
await tryInstallOrOpenGuide()
refreshCanInstall()
}
async function goNotifications() {
clearUnreadLocally()
@@ -77,6 +105,18 @@ async function goNotifications() {
function reloadApp() {
window.location.reload()
}
onMounted(() => {
initPwaInstallPromptListener()
refreshCanInstall()
window.addEventListener(PWA_MANIFEST_READY_EVENT, refreshCanInstall)
window.addEventListener('appinstalled', refreshCanInstall)
})
onUnmounted(() => {
window.removeEventListener(PWA_MANIFEST_READY_EVENT, refreshCanInstall)
window.removeEventListener('appinstalled', refreshCanInstall)
})
</script>
<style scoped>
@@ -58,6 +58,7 @@
<BasicIcon icon="vuesax-linear:edit-2" size="16" />
<span>ویرایش اطلاعات کاربری</span>
</button>
<InstallButton class="duser-profile__install" />
</section>
<section class="duser-surface duser-actions">
@@ -95,6 +96,7 @@
<script lang="ts" setup>
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import InstallButton from '@/components/InstallButton.vue'
import router from '@/router'
import { useUserPageContext } from '../composables/useUserPage'
@@ -184,6 +184,11 @@
}
}
}
&__install {
width: 100%;
margin-top: 10px;
}
}
.duser-actions {
@@ -60,6 +60,7 @@
<div>ویرایش اطلاعات کاربری</div>
</div>
</BasicButton>
<InstallButton class="!mt-1.5" />
<div class="w-full mt-4">
<BasicButton
v-for="action in menuActions"
@@ -91,6 +92,7 @@
import img from '@/assets/img/user_ly1.svg'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import InstallButton from '@/components/InstallButton.vue'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import { useUserPageContext } from '../composables/useUserPage'