mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
180 lines
7.9 KiB
TypeScript
180 lines
7.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { usePWAInstall } from '@/hooks/usePWAInstall'
|
|
import { usePlatform } from '@/hooks/usePlatform'
|
|
import { CloseIcon, ShareIcon, PlusCircleIcon } from '@/components/icons'
|
|
import { useTranslation } from '@/lib/use-translation'
|
|
|
|
export function InstallPrompt() {
|
|
const { t } = useTranslation()
|
|
const { canInstall, canInstallIOS, promptInstall, dismissInstall, isStandalone } = usePWAInstall()
|
|
const { isTelegram, haptic } = usePlatform()
|
|
const [showIOSGuide, setShowIOSGuide] = useState(false)
|
|
const showFloatingPrompt = !isTelegram && !isStandalone && ((canInstallIOS && !showIOSGuide) || canInstall)
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement
|
|
if (!root) return
|
|
|
|
if (showFloatingPrompt) {
|
|
root.style.setProperty('--app-floating-bottom-offset', '112px')
|
|
} else {
|
|
root.style.removeProperty('--app-floating-bottom-offset')
|
|
}
|
|
|
|
return () => {
|
|
root.style.removeProperty('--app-floating-bottom-offset')
|
|
}
|
|
}, [showFloatingPrompt])
|
|
|
|
if (isTelegram || isStandalone) {
|
|
return null
|
|
}
|
|
|
|
// iOS Safari install guide
|
|
if (canInstallIOS) {
|
|
if (showIOSGuide) {
|
|
return (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-end justify-center">
|
|
<div className="w-full max-w-lg bg-[var(--app-bg)] rounded-t-2xl p-5 pb-8 space-y-4 animate-slide-up">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-base font-semibold text-[var(--app-fg)]">
|
|
{t('install.title')}
|
|
</h3>
|
|
<button
|
|
onClick={() => setShowIOSGuide(false)}
|
|
className="p-1 -mr-1 text-[var(--app-hint)] active:opacity-60"
|
|
aria-label="Close"
|
|
>
|
|
<CloseIcon className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<div className="flex items-start gap-3">
|
|
<div className="shrink-0 w-8 h-8 rounded-full bg-[var(--app-fg)] text-[var(--app-bg)] flex items-center justify-center text-sm font-medium">
|
|
1
|
|
</div>
|
|
<div className="flex-1 pt-1">
|
|
<p className="text-sm text-[var(--app-fg)]">
|
|
Tap the <ShareIcon className="inline w-5 h-5 align-text-bottom" /> Share button in the toolbar
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<div className="shrink-0 w-8 h-8 rounded-full bg-[var(--app-fg)] text-[var(--app-bg)] flex items-center justify-center text-sm font-medium">
|
|
2
|
|
</div>
|
|
<div className="flex-1 pt-1">
|
|
<p className="text-sm text-[var(--app-fg)]">
|
|
Scroll down and tap <PlusCircleIcon className="inline w-5 h-5 align-text-bottom" /> <strong>Add to Home Screen</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<div className="shrink-0 w-8 h-8 rounded-full bg-[var(--app-fg)] text-[var(--app-bg)] flex items-center justify-center text-sm font-medium">
|
|
3
|
|
</div>
|
|
<div className="flex-1 pt-1">
|
|
<p className="text-sm text-[var(--app-fg)]">
|
|
Tap <strong>Add</strong> in the top right corner
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => {
|
|
setShowIOSGuide(false)
|
|
dismissInstall()
|
|
}}
|
|
className="w-full py-3 text-sm text-[var(--app-hint)] active:opacity-60"
|
|
>
|
|
{t('button.dismiss')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="fixed bottom-4 left-4 right-4 bg-[var(--app-secondary-bg)] border border-[var(--app-border)] rounded-lg p-4 shadow-lg z-50">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-[var(--app-fg)]">
|
|
{t('install.title')}
|
|
</p>
|
|
<p className="text-xs text-[var(--app-hint)] mt-0.5">
|
|
{t('install.description')}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => {
|
|
haptic.impact('light')
|
|
setShowIOSGuide(true)
|
|
}}
|
|
className="shrink-0 px-4 py-2 bg-[var(--app-fg)] text-[var(--app-bg)] rounded-lg text-sm font-medium active:opacity-80"
|
|
>
|
|
{t('install.button')}
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
haptic.impact('light')
|
|
dismissInstall()
|
|
}}
|
|
className="shrink-0 p-2 text-[var(--app-hint)] active:opacity-60"
|
|
aria-label="Dismiss"
|
|
>
|
|
<CloseIcon className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Chrome/Edge install prompt
|
|
if (!canInstall) {
|
|
return null
|
|
}
|
|
|
|
const handleInstall = async () => {
|
|
haptic.impact('light')
|
|
const success = await promptInstall()
|
|
if (success) {
|
|
haptic.notification('success')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="fixed bottom-4 left-4 right-4 bg-[var(--app-secondary-bg)] border border-[var(--app-border)] rounded-lg p-4 shadow-lg z-50">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-[var(--app-fg)]">
|
|
{t('install.title')}
|
|
</p>
|
|
<p className="text-xs text-[var(--app-hint)] mt-0.5">
|
|
{t('install.description')}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={handleInstall}
|
|
className="shrink-0 px-4 py-2 bg-[var(--app-fg)] text-[var(--app-bg)] rounded-lg text-sm font-medium active:opacity-80"
|
|
>
|
|
{t('install.button')}
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
haptic.impact('light')
|
|
dismissInstall()
|
|
}}
|
|
className="shrink-0 p-2 text-[var(--app-hint)] active:opacity-60"
|
|
aria-label="Dismiss"
|
|
>
|
|
<CloseIcon className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|