feat: add PWA install button to header
This commit is contained in:
parent
a04e8edb6e
commit
89d6a01b68
|
|
@ -0,0 +1,76 @@
|
|||
import React from "react";
|
||||
|
||||
/**
|
||||
* Compact PWA install button for the header.
|
||||
* - Shows 📲 icon when install prompt is available (Chrome/Edge on Android & desktop).
|
||||
* - Shows iOS Safari instructions tooltip on click when on iOS.
|
||||
* - Hidden when app is already installed (standalone mode).
|
||||
* - After install: auto-hidden via appinstalled event.
|
||||
*/
|
||||
export const PwaInstallButton = ({ onInstall, isInstalled, isInstallAvailable }) => {
|
||||
const [showTip, setShowTip] = React.useState(false);
|
||||
const tipRef = React.useRef(null);
|
||||
|
||||
// Detect iOS (no beforeinstallprompt, but can be added to home screen)
|
||||
const isIOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
const handleInstallClick = async () => {
|
||||
if (isInstallAvailable && onInstall) {
|
||||
await onInstall();
|
||||
} else {
|
||||
// Show instruction tip for iOS or unsupported browsers
|
||||
setShowTip((prev) => !prev);
|
||||
}
|
||||
};
|
||||
|
||||
// Close tip on outside click
|
||||
React.useEffect(() => {
|
||||
if (!showTip) return;
|
||||
const handler = (e) => {
|
||||
if (tipRef.current && !tipRef.current.contains(e.target)) {
|
||||
setShowTip(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", handler);
|
||||
return () => document.removeEventListener("click", handler);
|
||||
}, [showTip]);
|
||||
|
||||
// Don't render if already installed as PWA — AFTER all hooks
|
||||
if (isInstalled) return null;
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex" ref={tipRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleInstallClick}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-base transition hover:bg-[var(--color-accent-soft)]"
|
||||
aria-label="Установить приложение"
|
||||
title="Установить приложение"
|
||||
>
|
||||
📲
|
||||
</button>
|
||||
|
||||
{showTip && (
|
||||
<div className="absolute right-0 top-full z-50 mt-2 w-60 rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] p-3 text-sm shadow-lg">
|
||||
{isIOS ? (
|
||||
<>
|
||||
<p className="font-medium">Установка на iOS</p>
|
||||
<ol className="mt-1.5 list-decimal pl-4 leading-relaxed text-[var(--color-text-muted)]">
|
||||
<li>Откройте в <strong>Safari</strong></li>
|
||||
<li>Нажмите <strong>Поделиться</strong> ⬆️</li>
|
||||
<li>Выберите <strong>«На экран Домой»</strong></li>
|
||||
</ol>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="font-medium">Установка</p>
|
||||
<p className="mt-1 leading-relaxed text-[var(--color-text-muted)]">
|
||||
Нажмите значок 📲 в адресной строке браузера или используйте меню <strong>«Установить приложение»</strong>.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -4,11 +4,15 @@ import { Badge } from "../components/UI/Badge";
|
|||
import { Button } from "../components/UI/Button";
|
||||
import { Panel } from "../components/UI/Panel";
|
||||
import { ThemeToggle } from "../components/UI/ThemeToggle";
|
||||
import { PwaInstallButton } from "../components/UI/PwaInstallButton";
|
||||
import { NotificationBell } from "../components/notifications/NotificationBell";
|
||||
import { NotificationSettings } from "../components/notifications/NotificationSettings";
|
||||
|
||||
export const AppShell = ({
|
||||
user,
|
||||
onInstallApp,
|
||||
isInstalled,
|
||||
isInstallAvailable,
|
||||
onSignOut,
|
||||
onOpenGuide,
|
||||
isGuideOpen = false,
|
||||
|
|
@ -108,6 +112,7 @@ export const AppShell = ({
|
|||
{isGuideOpen ? "Назад" : "?"}
|
||||
</Button>
|
||||
) : null}
|
||||
<PwaInstallButton onInstall={onInstallApp} isInstalled={isInstalled} isInstallAvailable={isInstallAvailable} />
|
||||
<ThemeToggle />
|
||||
<Button size="sm" variant="ghost" onClick={onSignOut}>
|
||||
Выйти
|
||||
|
|
@ -146,6 +151,7 @@ export const AppShell = ({
|
|||
{isGuideOpen ? "Назад" : "?"}
|
||||
</Button>
|
||||
) : null}
|
||||
<PwaInstallButton onInstall={onInstallApp} isInstalled={isInstalled} isInstallAvailable={isInstallAvailable} />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ProductGuidePanel } from "../components/dashboard/ProductGuidePanel";
|
|||
import { useAuth } from "../context/AuthContext";
|
||||
import { useNotifications } from "../hooks/useNotifications";
|
||||
import { usePushNotifications } from "../hooks/usePushNotifications";
|
||||
import { usePwaStatus } from "../hooks/usePwaStatus";
|
||||
import { useOrderGroups } from "../hooks/useOrderGroups";
|
||||
import { AppShell } from "../layouts/AppShell";
|
||||
|
||||
|
|
@ -53,6 +54,8 @@ export const DashboardPage = () => {
|
|||
}
|
||||
}, [isSupported, isSubscribed, user?.id, subscribe]);
|
||||
|
||||
const { isInstalled, isInstallAvailable, installApp: onInstallApp } = usePwaStatus();
|
||||
|
||||
const {
|
||||
orderGroups,
|
||||
allOrderGroups,
|
||||
|
|
@ -143,6 +146,9 @@ export const DashboardPage = () => {
|
|||
<AppShell
|
||||
user={user}
|
||||
onSignOut={signOut}
|
||||
onInstallApp={onInstallApp}
|
||||
isInstalled={isInstalled}
|
||||
isInstallAvailable={isInstallAvailable}
|
||||
onOpenGuide={() => setActiveSection((current) => (current === "guide" ? section.key : "guide"))}
|
||||
isGuideOpen={isGuideOpen}
|
||||
navItems={navItems}
|
||||
|
|
|
|||
Loading…
Reference in New Issue