diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index 60f47fc..a760f46 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -8,6 +8,8 @@ import { hasLocale, NextIntlClientProvider } from 'next-intl'
import { notFound } from 'next/navigation'
import { routing } from '@/i18n/routing'
import MatomoTracking from '@/components/Matomo/MatomoTracking'
+import PwaContentProvider from '@/components/PwaContentProvider'
+import PwaInstallationDialog from '@/components/PwaInstallationDialog'
const inter = Inter({
weight: ['300', '400', '600', '700'],
@@ -48,17 +50,20 @@ export default async function LocaleLayout({
-
-
- {children}
-
+
+
+
+
+ {children}
+
+
diff --git a/src/components/PwaContentProvider.tsx b/src/components/PwaContentProvider.tsx
new file mode 100644
index 0000000..e742a35
--- /dev/null
+++ b/src/components/PwaContentProvider.tsx
@@ -0,0 +1,101 @@
+'use client'
+
+import usePwaInfo from '@/components/hooks/usePwaInfo'
+import { CustomDimensions } from '@/components/Matomo/constants'
+import useMatomo from '@/components/Matomo/useMatomo'
+import {
+ createContext,
+ MouseEventHandler,
+ ReactNode,
+ useCallback,
+ useEffect,
+ useState,
+} from 'react'
+
+interface PwaContextType {
+ handleInstallClick?: MouseEventHandler
+ showPwaInstallOptions: boolean
+ showSafariInstructions: boolean
+ prompt?: Event
+}
+
+export const PwaContext = createContext({
+ showPwaInstallOptions: false,
+ showSafariInstructions: false,
+})
+
+/**
+ * this provider contains PWA information needed for components
+ */
+export const PwaContentProvider = ({ children }: { children: ReactNode }) => {
+ const [showPwaInstallOptions, setShowPwaInstallOptions] =
+ useState(false)
+ const [showSafariInstructions, setShowSafariInstructions] =
+ useState(false)
+ const [prompt, setPrompt] = useState(undefined)
+
+ const { isPwa, hasChrome, hasSafari } = usePwaInfo()
+
+ const { trackEvent, setCustomDimension } = useMatomo()
+
+ useEffect(() => {
+ // set PWA state custom dimension
+ setCustomDimension(CustomDimensions.IS_PWA, isPwa ? 1 : 0)
+
+ // this is needed because chrome on mac contains both strings
+ if (!isPwa && !hasChrome && hasSafari) {
+ // safari
+ setShowSafariInstructions(true)
+ }
+
+ const handleBeforeInstallPrompt = (event: Event) => {
+ event.preventDefault()
+
+ if (!isPwa && hasChrome) {
+ // chrome
+ setShowPwaInstallOptions(true)
+ // is needed in order to open the installation prompt
+ setPrompt(event)
+ }
+ }
+
+ const handleAfterInstallPrompt = () => {
+ trackEvent('PwaInstallation', 'PWA installed')
+ setShowPwaInstallOptions(false)
+ }
+
+ window?.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
+ window?.addEventListener('appinstalled', handleAfterInstallPrompt)
+
+ return () => {
+ window?.removeEventListener(
+ 'beforeinstallprompt',
+ handleBeforeInstallPrompt
+ )
+ window?.removeEventListener('appinstalled', handleAfterInstallPrompt)
+ }
+ }, [hasChrome, hasSafari, isPwa, setCustomDimension, trackEvent])
+
+ const handleInstallClick = useCallback(() => {
+ if (prompt) {
+ trackEvent('PwaInstallation', 'click', 'installButton')
+ // @ts-expect-error actually we expect here the BeforeInstallPromptEvent but it is not supported by at least firefox
+ prompt?.prompt()
+ }
+ }, [prompt, trackEvent])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export default PwaContentProvider
diff --git a/src/components/PwaInstallation.tsx b/src/components/PwaInstallation.tsx
index c866386..d8c6af1 100644
--- a/src/components/PwaInstallation.tsx
+++ b/src/components/PwaInstallation.tsx
@@ -1,87 +1,20 @@
'use client'
-import { CustomDimensions } from '@/components/Matomo/constants'
-import useMatomo from '@/components/Matomo/useMatomo'
-import { AddBoxOutlined, InstallMobile, IosShare } from '@mui/icons-material'
-import {
- Alert,
- Button,
- Grid,
- List,
- ListItem,
- ListItemText,
- Paper,
- Typography,
-} from '@mui/material'
+import { PwaContext } from '@/components/PwaContentProvider'
+import PwaInstallationButtonChromium from '@/components/PwaInstallationButtonChromium'
+import PwaInstallationInstructionIos from '@/components/PwaInstallationInstructionIos'
+import { Alert, Grid, Typography } from '@mui/material'
import { useTranslations } from 'next-intl'
-import { useCallback, useEffect, useState } from 'react'
+import { useContext } from 'react'
/**
* this component contains the PWA installation
*/
const PwaInstallation = () => {
- const [showInstallButton, setShowInstallButton] = useState(false)
- const [showInstallationInstruction, setShowInstallationInstruction] =
- useState(false)
- const [prompt, setPrompt] = useState(null)
+ const { handleInstallClick, showPwaInstallOptions, showSafariInstructions } =
+ useContext(PwaContext)
- const browser = window.navigator.userAgent
- const isPWA = window.matchMedia('(display-mode: standalone)').matches
- const hasChrome = browser.includes('Chrome')
- const hasSafari = browser.includes('Safari')
-
- const { trackEvent, setCustomDimension } = useMatomo()
-
- useEffect(() => {
- // set PWA state custom dimension
- setCustomDimension(CustomDimensions.IS_PWA, isPWA ? 1 : 0)
-
- // this is needed because chrome on mac contains both strings
- if (!isPWA && !hasChrome && hasSafari) {
- // safari
- setShowInstallButton(true)
- }
-
- const handleBeforeInstallPrompt = (event: Event) => {
- event.preventDefault()
-
- if (!isPWA && hasChrome) {
- // chrome
- setShowInstallButton(true)
- // is needed in order to open the installation prompt
- setPrompt(event)
- }
- }
-
- const handleAfterInstallPrompt = () => {
- trackEvent('PwaInstallation', 'PWA installed')
- setShowInstallButton(false)
- }
-
- window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt)
- window.addEventListener('appinstalled', handleAfterInstallPrompt)
-
- return () => {
- window.removeEventListener(
- 'beforeinstallprompt',
- handleBeforeInstallPrompt
- )
- window.removeEventListener('appinstalled', handleAfterInstallPrompt)
- }
- }, [hasChrome, hasSafari, isPWA, setCustomDimension, trackEvent])
-
- const handleInstallClick = useCallback(() => {
- if (prompt) {
- trackEvent('PwaInstallation', 'click', 'installButton')
- // @ts-expect-error actually we expect here the BeforeInstallPromptEvent but it is not supported by at least firefox
- prompt?.prompt()
- } else if (!hasChrome && hasSafari) {
- setShowInstallButton(false)
- setShowInstallationInstruction(true)
- }
- }, [prompt, hasChrome, hasSafari, trackEvent])
-
- const t = useTranslations('PWAInstallation')
+ const t = useTranslations('pwa.install')
return (
{
pb={7.5}
sx={{ height: '100%' }}
>
- {showInstallButton && (
+ {showPwaInstallOptions && (
- }
- onClick={handleInstallClick}
- sx={{ py: 1, color: 'communication.hyperlink.main' }}
- >
-
- {t('install.button.label')}
-
-
+ {/* @ts-expect-error ignore undefined */}
+
)}
- {showInstallationInstruction && (
+ {showSafariInstructions && (
{
})}
>
- {t('install.instruction.title')}
+ {t('instruction.title')}
-
-
-
-
-
-
- {t('install.instruction.step1')}{' '}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('install.instruction.step2')}
-
-
-
-
- {t('install.instruction.toHomeScreen')}
-
-
-
-
-
-
-
+
diff --git a/src/components/PwaInstallationButtonChromium.tsx b/src/components/PwaInstallationButtonChromium.tsx
new file mode 100644
index 0000000..d9c8ae1
--- /dev/null
+++ b/src/components/PwaInstallationButtonChromium.tsx
@@ -0,0 +1,33 @@
+'use client'
+
+import { InstallMobile } from '@mui/icons-material'
+import { Button, Typography } from '@mui/material'
+import { useTranslations } from 'next-intl'
+import { MouseEventHandler } from 'react'
+
+/**
+ * this component contains the PWA installation button
+ * shown on chromium based browsers
+ */
+const PwaInstallationButtonChromium = ({
+ onClick,
+}: {
+ onClick: MouseEventHandler
+}) => {
+ const t = useTranslations('pwa.install')
+
+ return (
+ // @ts-expect-error for some reason now it does not like missing href
+ }
+ onClick={onClick}
+ sx={{ py: 1, color: 'communication.hyperlink.main' }}
+ >
+
+ {t('button.label')}
+
+
+ )
+}
+
+export default PwaInstallationButtonChromium
diff --git a/src/components/PwaInstallationDialog.tsx b/src/components/PwaInstallationDialog.tsx
new file mode 100644
index 0000000..53e2bd6
--- /dev/null
+++ b/src/components/PwaInstallationDialog.tsx
@@ -0,0 +1,113 @@
+'use client'
+
+import useMatomo from '@/components/Matomo/useMatomo'
+import { PwaContext } from '@/components/PwaContentProvider'
+import PwaInstallationButtonChromium from '@/components/PwaInstallationButtonChromium'
+import PwaInstallationInstructionIos from '@/components/PwaInstallationInstructionIos'
+import { Close } from '@mui/icons-material'
+import {
+ Avatar,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ Grid,
+ IconButton,
+ Typography,
+} from '@mui/material'
+import { useTranslations } from 'next-intl'
+import { useContext, useState } from 'react'
+
+/**
+ * this component contains the PWA installation
+ */
+const PwaInstallationDialog = () => {
+ const { handleInstallClick, showPwaInstallOptions, showSafariInstructions } =
+ useContext(PwaContext)
+ const [open, setOpen] = useState(true)
+ const { trackEvent } = useMatomo()
+
+ const t = useTranslations('pwa.dialog')
+
+ return (
+ <>
+ {open && (showPwaInstallOptions || showSafariInstructions) && (
+
+ )}
+ >
+ )
+}
+
+export default PwaInstallationDialog
diff --git a/src/components/PwaInstallationInstructionIos.tsx b/src/components/PwaInstallationInstructionIos.tsx
new file mode 100644
index 0000000..0e3b9ae
--- /dev/null
+++ b/src/components/PwaInstallationInstructionIos.tsx
@@ -0,0 +1,69 @@
+'use client'
+
+import { AddBoxOutlined, IosShare } from '@mui/icons-material'
+import {
+ Grid,
+ List,
+ ListItem,
+ ListItemText,
+ Paper,
+ Typography,
+} from '@mui/material'
+import { useTranslations } from 'next-intl'
+
+/**
+ * this component contains the PWA installation instruction for safari
+ */
+const PwaInstallationInstructionIos = () => {
+ const t = useTranslations('pwa.install.instruction')
+
+ return (
+
+
+
+
+
+
+ {t('step1')}{' '}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('step2')}
+
+
+
+ {t('toHomeScreen')}
+
+
+
+
+
+
+ )
+}
+
+export default PwaInstallationInstructionIos
diff --git a/src/components/hooks/usePwaInfo.ts b/src/components/hooks/usePwaInfo.ts
new file mode 100644
index 0000000..7e2a304
--- /dev/null
+++ b/src/components/hooks/usePwaInfo.ts
@@ -0,0 +1,16 @@
+import { useMemo } from 'react'
+
+export default function usePwaInfo() {
+ const pwaInfos = useMemo(() => {
+ if (typeof window !== 'undefined') {
+ const browser = window.navigator.userAgent
+ const isPwa = window.matchMedia('(display-mode: standalone)').matches
+ const hasChrome = browser.includes('Chrome')
+ const hasSafari = browser.includes('Safari')
+ return { isPwa, hasChrome, hasSafari }
+ }
+ return {}
+ }, [])
+
+ return { ...pwaInfos }
+}
diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json
index e8ab553..f09d2a6 100644
--- a/src/i18n/messages/de.json
+++ b/src/i18n/messages/de.json
@@ -39,7 +39,12 @@
"BackButton": {
"label": "zurück"
},
- "PWAInstallation": {
+ "pwa": {
+ "dialog": {
+ "title": "appgefahren! WebApp installieren",
+ "iosText": "Füge die appgefahren! WebApp deinem Home-Bildschirm hinzu, um sie immer dabei zu haben.",
+ "chromiumText": "Installiere die appgefahren! WebApp, um sie immer dabei zu haben."
+ },
"install": {
"button": {
"label": "WebApp installieren"
@@ -49,8 +54,8 @@
"step1": "Tippe auf ",
"step2": "wähle ",
"toHomeScreen": "Zum Home-Bildschirm ",
- "iosShareIcon": "Teilen-Button",
- "addBoxIconOutlinedIcon": "hinzufügen-Button"
+ "titleAccessIosShareIcon": "Teilen-Button",
+ "titleAccessAddBoxIconOutlinedIcon": "hinzufügen-Button"
}
}
},