mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 09:02:23 +00:00
feat(frontend): Add comprehensive PWA capabilities with dynamic theme support
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
committed by
Elisiário Couto
parent
81d7d16301
commit
86891441d6
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
dev-dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
|
||||
@@ -2,9 +2,35 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="48x48" />
|
||||
<link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>Leggen</title>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Personal finance management application" />
|
||||
<meta name="application-name" content="Leggen" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="Leggen" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="msapplication-TileColor" content="#3B82F6" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<!-- Dynamic theme-color - will be updated by JavaScript -->
|
||||
<meta name="theme-color" content="#ffffff" id="theme-color-meta" />
|
||||
<meta name="msapplication-navbutton-color" content="#ffffff" id="ms-theme-color-meta" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" id="apple-status-bar-meta" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#3B82F6" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
|
||||
<!-- Manifest -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
5005
frontend/package-lock.json
generated
5005
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -42,13 +42,16 @@
|
||||
"@tanstack/router-vite-plugin": "^1.131.36",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@vite-pwa/assets-generator": "^1.0.1",
|
||||
"@vitejs/plugin-react": "^5.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"sharp": "^0.34.3",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2"
|
||||
"vite": "^7.1.2",
|
||||
"vite-plugin-pwa": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
frontend/public/apple-touch-icon-180x180.png
Normal file
BIN
frontend/public/apple-touch-icon-180x180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 550 B |
9
frontend/public/browserconfig.xml
Normal file
9
frontend/public/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/pwa-192x192.png"/>
|
||||
<TileColor>#3B82F6</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 473 B |
BIN
frontend/public/maskable-icon-512x512.png
Normal file
BIN
frontend/public/maskable-icon-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
frontend/public/pwa-192x192.png
Normal file
BIN
frontend/public/pwa-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 676 B |
BIN
frontend/public/pwa-512x512.png
Normal file
BIN
frontend/public/pwa-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
frontend/public/pwa-64x64.png
Normal file
BIN
frontend/public/pwa-64x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 352 B |
4
frontend/pwa-assets.config.json
Normal file
4
frontend/pwa-assets.config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"preset": "minimal-2023",
|
||||
"images": ["public/favicon.svg"]
|
||||
}
|
||||
@@ -10,6 +10,12 @@ interface ThemeContextType {
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
// Theme colors for different modes
|
||||
const THEME_COLORS = {
|
||||
light: "#ffffff",
|
||||
dark: "#0f0f23", // Dark background color that matches typical dark themes
|
||||
} as const;
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
const stored = localStorage.getItem("theme") as Theme;
|
||||
@@ -40,6 +46,28 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// Add resolved theme class
|
||||
root.classList.add(resolvedTheme);
|
||||
|
||||
// Update theme-color meta tags for PWA status bar
|
||||
const themeColor = THEME_COLORS[resolvedTheme];
|
||||
|
||||
// Update theme-color meta tag
|
||||
const themeColorMeta = document.getElementById("theme-color-meta") as HTMLMetaElement;
|
||||
if (themeColorMeta) {
|
||||
themeColorMeta.content = themeColor;
|
||||
}
|
||||
|
||||
// Update Microsoft tile color
|
||||
const msThemeColorMeta = document.getElementById("ms-theme-color-meta") as HTMLMetaElement;
|
||||
if (msThemeColorMeta) {
|
||||
msThemeColorMeta.content = themeColor;
|
||||
}
|
||||
|
||||
// Update Apple status bar style for better iOS integration
|
||||
const appleStatusBarMeta = document.getElementById("apple-status-bar-meta") as HTMLMetaElement;
|
||||
if (appleStatusBarMeta) {
|
||||
// Use 'black-translucent' for dark theme, 'default' for light theme
|
||||
appleStatusBarMeta.content = resolvedTheme === "dark" ? "black-translucent" : "default";
|
||||
}
|
||||
};
|
||||
|
||||
updateActualTheme();
|
||||
|
||||
37
frontend/src/hooks/usePWA.ts
Normal file
37
frontend/src/hooks/usePWA.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface PWAUpdate {
|
||||
updateAvailable: boolean;
|
||||
updateSW: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function usePWA(): PWAUpdate {
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||
const [updateSW, setUpdateSW] = useState<() => Promise<void>>(() => async () => {});
|
||||
|
||||
useEffect(() => {
|
||||
// Check if SW registration is available
|
||||
if ("serviceWorker" in navigator) {
|
||||
// Import the registerSW function
|
||||
import("virtual:pwa-register").then(({ registerSW }) => {
|
||||
const updateSWFunction = registerSW({
|
||||
onNeedRefresh() {
|
||||
setUpdateAvailable(true);
|
||||
setUpdateSW(() => updateSWFunction);
|
||||
},
|
||||
onOfflineReady() {
|
||||
console.log("App ready to work offline");
|
||||
},
|
||||
});
|
||||
}).catch(() => {
|
||||
// PWA not available in development mode or when disabled
|
||||
console.log("PWA registration not available");
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
updateAvailable,
|
||||
updateSW,
|
||||
};
|
||||
}
|
||||
1
frontend/src/vite-env.d.ts
vendored
1
frontend/src/vite-env.d.ts
vendored
@@ -1 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-pwa/client" />
|
||||
|
||||
@@ -1,10 +1,72 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { TanStackRouterVite } from "@tanstack/router-vite-plugin";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite(), react()],
|
||||
plugins: [
|
||||
TanStackRouterVite(),
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
includeAssets: ["favicon.ico", "apple-touch-icon-180x180.png", "maskable-icon-512x512.png"],
|
||||
manifest: {
|
||||
name: "Leggen",
|
||||
short_name: "Leggen",
|
||||
description: "Personal finance management application",
|
||||
theme_color: "#ffffff",
|
||||
background_color: "#ffffff",
|
||||
display: "standalone",
|
||||
orientation: "portrait",
|
||||
scope: "/",
|
||||
start_url: "/",
|
||||
categories: ["finance", "productivity"],
|
||||
icons: [
|
||||
{
|
||||
src: "pwa-64x64.png",
|
||||
sizes: "64x64",
|
||||
type: "image/png"
|
||||
},
|
||||
{
|
||||
src: "pwa-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png"
|
||||
},
|
||||
{
|
||||
src: "pwa-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png"
|
||||
},
|
||||
{
|
||||
src: "maskable-icon-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable"
|
||||
}
|
||||
],
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ["**/*.{js,css,html,ico,png,svg}"],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/.*\/api\//,
|
||||
handler: "NetworkFirst",
|
||||
options: {
|
||||
cacheName: "api-cache",
|
||||
networkTimeoutSeconds: 10,
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
devOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": "/src",
|
||||
|
||||
Reference in New Issue
Block a user