diff --git a/resources/js/components/landing/install-app-button.tsx b/resources/js/components/landing/install-app-button.tsx
new file mode 100644
index 00000000..4c0e0846
--- /dev/null
+++ b/resources/js/components/landing/install-app-button.tsx
@@ -0,0 +1,95 @@
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import { usePwaInstall } from '@/hooks/use-pwa-install';
+import { DownloadIcon, PlusSquareIcon, ShareIcon } from 'lucide-react';
+import { useState } from 'react';
+
+export default function InstallAppButton() {
+ const { platform, canInstall, promptInstall } = usePwaInstall();
+ const [showIosDialog, setShowIosDialog] = useState(false);
+
+ if (platform === 'android') {
+ return (
+
+ );
+ }
+
+ if (platform === 'ios') {
+ return (
+ <>
+
+
+
+ >
+ );
+ }
+
+ return null;
+}
diff --git a/resources/js/hooks/use-pwa-install.ts b/resources/js/hooks/use-pwa-install.ts
new file mode 100644
index 00000000..a1b2b0cb
--- /dev/null
+++ b/resources/js/hooks/use-pwa-install.ts
@@ -0,0 +1,73 @@
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+type Platform = 'android' | 'ios' | null;
+
+interface BeforeInstallPromptEvent extends Event {
+ prompt(): Promise;
+ userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;
+}
+
+function detectPlatform(): Platform {
+ if (typeof navigator === 'undefined') {
+ return null;
+ }
+
+ const ua = navigator.userAgent;
+
+ if (/android/i.test(ua)) {
+ return 'android';
+ }
+
+ if (
+ /iPad|iPhone|iPod/.test(ua) ||
+ (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
+ ) {
+ return 'ios';
+ }
+
+ return null;
+}
+
+export function usePwaInstall() {
+ const [platform] = useState(detectPlatform);
+ const deferredPromptRef = useRef(null);
+ const [canInstall, setCanInstall] = useState(false);
+
+ useEffect(() => {
+ if (platform !== 'android') {
+ return;
+ }
+
+ const handler = (e: Event) => {
+ e.preventDefault();
+ deferredPromptRef.current = e as BeforeInstallPromptEvent;
+ setCanInstall(true);
+ };
+
+ window.addEventListener('beforeinstallprompt', handler);
+
+ return () => window.removeEventListener('beforeinstallprompt', handler);
+ }, [platform]);
+
+ const promptInstall = useCallback(async (): Promise => {
+ const prompt = deferredPromptRef.current;
+
+ if (!prompt) {
+ return false;
+ }
+
+ await prompt.prompt();
+ const { outcome } = await prompt.userChoice;
+ deferredPromptRef.current = null;
+ setCanInstall(false);
+
+ return outcome === 'accepted';
+ }, []);
+
+ return {
+ platform,
+ isMobile: platform !== null,
+ canInstall,
+ promptInstall,
+ };
+}
diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx
index 519e8a7f..e6b6c3c2 100644
--- a/resources/js/pages/welcome.tsx
+++ b/resources/js/pages/welcome.tsx
@@ -1,4 +1,5 @@
import EncryptionVideoPlayer from '@/components/landing/encryption-video-player';
+import InstallAppButton from '@/components/landing/install-app-button';
import Header from '@/components/partials/header';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
@@ -8,6 +9,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
+import { usePwaInstall } from '@/hooks/use-pwa-install';
import { LEAD_FUNNEL_EVENT_UUID } from '@/lib/constants';
import { trackEvent } from '@/lib/track-event';
import { cn } from '@/lib/utils';
@@ -160,6 +162,7 @@ export default function Welcome({
usePage().props;
const planEntries = Object.entries(pricing.plans);
const visitTrackedRef = useRef(false);
+ const { isMobile } = usePwaInstall();
const [isPwa] = useState(() => {
if (typeof window === 'undefined') {
@@ -303,25 +306,29 @@ export default function Welcome({
secure.
-
-
-
-
-
-
+ ) : (
+
+
- Check Demo
-
-
-
+
+
+
+
+
+
+ )}
Your data is yours alone. Sign up to get
started.