From b31d488e255f150ce6a4c84f04e08a58c7ad1cc8 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Fri, 5 Dec 2025 02:03:56 -0500 Subject: [PATCH] feat: add PWA manifest and Apple icon, and enhance map with fly-to selected shop functionality. --- src/app/apple-icon.tsx | 34 ++++++++++++++++++++++++++++++++++ src/app/layout.tsx | 10 +++++++++- src/app/manifest.ts | 25 +++++++++++++++++++++++++ src/app/page.tsx | 1 + src/components/Map.tsx | 21 +++++++++++++++++++-- 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/app/apple-icon.tsx create mode 100644 src/app/manifest.ts diff --git a/src/app/apple-icon.tsx b/src/app/apple-icon.tsx new file mode 100644 index 0000000..85e7696 --- /dev/null +++ b/src/app/apple-icon.tsx @@ -0,0 +1,34 @@ +import { ImageResponse } from 'next/og'; + +export const runtime = 'edge'; + +export const size = { + width: 32, + height: 32, +}; +export const contentType = 'image/png'; + +export default function Icon() { + return new ImageResponse( + ( +
+ ☕ +
+ ), + { + ...size, + } + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c08fc77..f02e8c0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,6 @@ import "~/styles/globals.css"; -import { type Metadata } from "next"; +import { type Metadata, type Viewport } from "next"; import { PT_Serif } from "next/font/google"; import { ThemeProvider } from "~/components/ThemeProvider"; @@ -11,6 +11,14 @@ export const metadata: Metadata = { icons: [{ rel: "icon", url: "/favicon.ico" }], }; +export const viewport: Viewport = { + themeColor: "#8B4513", + width: "device-width", + initialScale: 1, + maximumScale: 1, + userScalable: false, +}; + const ptSerif = PT_Serif({ subsets: ["latin"], weight: ["400", "700"], diff --git a/src/app/manifest.ts b/src/app/manifest.ts new file mode 100644 index 0000000..ab7a170 --- /dev/null +++ b/src/app/manifest.ts @@ -0,0 +1,25 @@ +import { type MetadataRoute } from 'next'; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'Lewisburg Coffee Map', + short_name: 'Coffee Map', + description: 'Find the best coffee in Lewisburg, PA', + start_url: '/', + display: 'standalone', + background_color: '#ffffff', + theme_color: '#8B4513', + icons: [ + { + src: '/favicon.ico', + sizes: 'any', + type: 'image/x-icon', + }, + { + src: '/icon.svg', + sizes: 'any', + type: 'image/svg+xml', + } + ], + }; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 98930cf..1422364 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -17,6 +17,7 @@ export default function HomePage() { setSelectedShop(shop)} + selectedShop={selectedShop} /> diff --git a/src/components/Map.tsx b/src/components/Map.tsx index 0912214..9236fc1 100644 --- a/src/components/Map.tsx +++ b/src/components/Map.tsx @@ -1,6 +1,6 @@ "use client"; -import { MapContainer, TileLayer, Marker } from 'react-leaflet'; +import { MapContainer, TileLayer, Marker, useMap } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; import L from 'leaflet'; import { useState, useEffect } from 'react'; @@ -24,9 +24,25 @@ interface CoffeeShop { interface MapProps { shops: CoffeeShop[]; onShopSelect: (shop: CoffeeShop) => void; + selectedShop: CoffeeShop | null; } -const Map = ({ shops, onShopSelect }: MapProps) => { +const MapController = ({ selectedShop }: { selectedShop: CoffeeShop | null }) => { + const map = useMap(); + + useEffect(() => { + if (selectedShop) { + map.flyTo([selectedShop.lat, selectedShop.lng], 16, { + duration: 1.5, + easeLinearity: 0.25, + }); + } + }, [selectedShop, map]); + + return null; +}; + +const Map = ({ shops, onShopSelect, selectedShop }: MapProps) => { useEffect(() => { // Fix for Leaflet default icon not found // @ts-expect-error Fix for Leaflet default icon not found @@ -107,6 +123,7 @@ const Map = ({ shops, onShopSelect }: MapProps) => { attributionControl={false} > +