From 3049a8cd2fa80c14f970884fb14df2ab88c418dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 01:18:18 +0000 Subject: [PATCH] feat(frontend): Add PWA install prompts, update notifications, and app shortcuts Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com> --- frontend/public/robots.txt | 4 + frontend/src/components/PWAPrompts.tsx | 156 +++++++++++++++++++++++++ frontend/src/routes/__root.tsx | 23 ++++ frontend/vite.config.ts | 18 ++- 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 frontend/public/robots.txt create mode 100644 frontend/src/components/PWAPrompts.tsx diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..446726b --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: /sitemap.xml \ No newline at end of file diff --git a/frontend/src/components/PWAPrompts.tsx b/frontend/src/components/PWAPrompts.tsx new file mode 100644 index 0000000..3e66008 --- /dev/null +++ b/frontend/src/components/PWAPrompts.tsx @@ -0,0 +1,156 @@ +import { useEffect, useState } from "react"; +import { X, Download, RotateCcw } from "lucide-react"; + +interface BeforeInstallPromptEvent extends Event { + prompt(): Promise; + userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>; +} + +interface PWAPromptProps { + onInstall?: () => void; +} + +export function PWAInstallPrompt({ onInstall }: PWAPromptProps) { + const [deferredPrompt, setDeferredPrompt] = useState(null); + const [showPrompt, setShowPrompt] = useState(false); + + useEffect(() => { + const handler = (e: Event) => { + // Prevent the mini-infobar from appearing on mobile + e.preventDefault(); + setDeferredPrompt(e as BeforeInstallPromptEvent); + setShowPrompt(true); + }; + + window.addEventListener("beforeinstallprompt", handler); + + return () => window.removeEventListener("beforeinstallprompt", handler); + }, []); + + const handleInstall = async () => { + if (!deferredPrompt) return; + + try { + await deferredPrompt.prompt(); + const { outcome } = await deferredPrompt.userChoice; + + if (outcome === "accepted") { + onInstall?.(); + } + + setDeferredPrompt(null); + setShowPrompt(false); + } catch (error) { + console.error("Error installing PWA:", error); + } + }; + + const handleDismiss = () => { + setShowPrompt(false); + setDeferredPrompt(null); + }; + + if (!showPrompt || !deferredPrompt) return null; + + return ( +
+
+
+ +
+
+

+ Install Leggen +

+

+ Add to your home screen for quick access +

+
+ +
+
+ + +
+
+ ); +} + +interface PWAUpdatePromptProps { + updateAvailable: boolean; + onUpdate: () => void; +} + +export function PWAUpdatePrompt({ updateAvailable, onUpdate }: PWAUpdatePromptProps) { + const [showPrompt, setShowPrompt] = useState(false); + + useEffect(() => { + if (updateAvailable) { + setShowPrompt(true); + } + }, [updateAvailable]); + + const handleUpdate = () => { + onUpdate(); + setShowPrompt(false); + }; + + const handleDismiss = () => { + setShowPrompt(false); + }; + + if (!showPrompt || !updateAvailable) return null; + + return ( +
+
+
+ +
+
+

+ Update Available +

+

+ A new version of Leggen is ready to install +

+
+ +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 044adc1..252beaf 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -2,9 +2,25 @@ import { createRootRoute, Outlet } from "@tanstack/react-router"; import { useState } from "react"; import Sidebar from "../components/Sidebar"; import Header from "../components/Header"; +import { PWAInstallPrompt, PWAUpdatePrompt } from "../components/PWAPrompts"; +import { usePWA } from "../hooks/usePWA"; function RootLayout() { const [sidebarOpen, setSidebarOpen] = useState(false); + const { updateAvailable, updateSW } = usePWA(); + + const handlePWAInstall = () => { + console.log("PWA installed successfully"); + }; + + const handlePWAUpdate = async () => { + try { + await updateSW(); + console.log("PWA updated successfully"); + } catch (error) { + console.error("Error updating PWA:", error); + } + }; return (
@@ -24,6 +40,13 @@ function RootLayout() {
+ + {/* PWA Prompts */} + + ); } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 7d31788..c1e0e92 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ react(), VitePWA({ registerType: "autoUpdate", - includeAssets: ["favicon.ico", "apple-touch-icon-180x180.png", "maskable-icon-512x512.png"], + includeAssets: ["favicon.ico", "apple-touch-icon-180x180.png", "maskable-icon-512x512.png", "robots.txt"], manifest: { name: "Leggen", short_name: "Leggen", @@ -22,6 +22,22 @@ export default defineConfig({ scope: "/", start_url: "/", categories: ["finance", "productivity"], + shortcuts: [ + { + name: "Transactions", + short_name: "Transactions", + description: "View and manage transactions", + url: "/transactions", + icons: [{ src: "/pwa-192x192.png", sizes: "192x192" }] + }, + { + name: "Analytics", + short_name: "Analytics", + description: "View financial analytics", + url: "/analytics", + icons: [{ src: "/pwa-192x192.png", sizes: "192x192" }] + } + ], icons: [ { src: "pwa-64x64.png",