From d78893890f20c85a731880518790295e6bbf8432 Mon Sep 17 00:00:00 2001 From: David Abell <79927957+david-abell@users.noreply.github.com> Date: Mon, 27 May 2024 09:26:16 +0000 Subject: [PATCH] feat: Changelog component (#42) * package lock version update * add shadcn dialog * Feat: add changelog component * increase dialog z-index * fix: indent wrapped title text and list items * fix: list overflow scroll and prevent fullscreen at small screen sizes * add recent changelogs * update usehooks-ts * fix: changelog overflow, only show on newUser/changes available * add spelling exception to workspace * fix: replace deprecated useElementSize hook * fix: changelog spelling * add changelog nav button * fix: more changelog text updates --- .vscode/settings.json | 1 + package-lock.json | 38 +++++--- package.json | 4 +- src/components/changelog/Changelog.tsx | 69 ++++++++++++++ src/components/changelog/Version.tsx | 21 +++++ src/components/changelog/changelogs.ts | 48 ++++++++++ src/components/ui/dialog.tsx | 120 +++++++++++++++++++++++++ src/pages/index.tsx | 18 +++- 8 files changed, 302 insertions(+), 17 deletions(-) create mode 100644 src/components/changelog/Changelog.tsx create mode 100644 src/components/changelog/Version.tsx create mode 100644 src/components/changelog/changelogs.ts create mode 100644 src/components/ui/dialog.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index fcdbab1..a9f9520 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "luxon", "moveend", "ontime", + "Openchange", "papaparse", "popupclose", "popupopen", diff --git a/package-lock.json b/package-lock.json index a6206d5..4eef79b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "irish-buses", - "version": "0.10.0", + "version": "0.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "irish-buses", - "version": "0.10.0", + "version": "0.12.0", "dependencies": { "@prisma/client": "^5.9.0", "@radix-ui/react-accordion": "^1.1.2", @@ -57,7 +57,7 @@ "tailwindcss-animate": "^1.0.7", "ts-retry": "^4.2.5", "typescript": "^5.3.3", - "usehooks-ts": "^2.10.0", + "usehooks-ts": "^2.16.0", "vaul": "^0.9.1", "zod": "^3.21.4" }, @@ -7743,6 +7743,11 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "dev": true }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -11040,15 +11045,17 @@ } }, "node_modules/usehooks-ts": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.10.0.tgz", - "integrity": "sha512-hN22L5pqcsM1LwL6Q3p1QfxI9K+wuP4lrBXyCQ4Vi5Zo94YhphqRh2l7G7uhqPzLunAObLUAoSKzo64lisNyWQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.16.0.tgz", + "integrity": "sha512-bez95WqYujxp6hFdM/CpRDiVPirZPxlMzOH2QB8yopoKQMXpscyZoxOjpEdaxvV+CAWUDSM62cWnqHE0E/MZ7w==", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, "engines": { "node": ">=16.15.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17 || ^18" } }, "node_modules/utf8-byte-length": { @@ -16835,6 +16842,11 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -19098,10 +19110,12 @@ "requires": {} }, "usehooks-ts": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.10.0.tgz", - "integrity": "sha512-hN22L5pqcsM1LwL6Q3p1QfxI9K+wuP4lrBXyCQ4Vi5Zo94YhphqRh2l7G7uhqPzLunAObLUAoSKzo64lisNyWQ==", - "requires": {} + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.16.0.tgz", + "integrity": "sha512-bez95WqYujxp6hFdM/CpRDiVPirZPxlMzOH2QB8yopoKQMXpscyZoxOjpEdaxvV+CAWUDSM62cWnqHE0E/MZ7w==", + "requires": { + "lodash.debounce": "^4.0.8" + } }, "utf8-byte-length": { "version": "1.0.4", diff --git a/package.json b/package.json index e2ba612..1f32649 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "irish-buses", - "version": "0.11.2", + "version": "0.12.0", "type": "module", "scripts": { "dev": "next dev --experimental-https", @@ -61,7 +61,7 @@ "tailwindcss-animate": "^1.0.7", "ts-retry": "^4.2.5", "typescript": "^5.3.3", - "usehooks-ts": "^2.10.0", + "usehooks-ts": "^2.16.0", "vaul": "^0.9.1", "zod": "^3.21.4" }, diff --git a/src/components/changelog/Changelog.tsx b/src/components/changelog/Changelog.tsx new file mode 100644 index 0000000..a3e77d2 --- /dev/null +++ b/src/components/changelog/Changelog.tsx @@ -0,0 +1,69 @@ +import { + Dialog, + DialogContent, + DialogClose, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { changelogs } from "./changelogs"; +import Version from "./Version"; +import { useLocalStorage } from "usehooks-ts"; +import packageJson from "package.json"; +import { useEffect } from "react"; +const packageAppVersion = packageJson.version; + +const VERSION_KEY = "app-version"; +const NEW_USER_KEY = "new-user"; + +type Props = { + show: boolean; + setShow: (s: boolean) => void; +}; + +function Changelog({ show, setShow }: Props) { + const [appVersion, setAppVersion] = useLocalStorage(VERSION_KEY, "default"); + const [newUser, setNewUser] = useLocalStorage(NEW_USER_KEY, "default"); + + // Radix onOpenchange only triggers on dialog close + const handleOpenChange = () => { + setAppVersion(packageAppVersion); + setShow(false); + setNewUser("false"); + }; + + useEffect(() => { + if (appVersion === "default") { + setAppVersion(packageAppVersion); + } else if (appVersion === packageAppVersion && newUser === "default") { + setNewUser("true"); + setShow(true); + } else if (appVersion !== packageAppVersion) { + setShow(true); + } + }, [appVersion, newUser, setAppVersion, setNewUser, setShow]); + + if (appVersion === "default" || show === false) return null; + + return ( + + Open + + + Changelog + + + {changelogs.map((record) => ( + + ))} + + + Dismiss + + + + ); +} + +export default Changelog; diff --git a/src/components/changelog/Version.tsx b/src/components/changelog/Version.tsx new file mode 100644 index 0000000..19b51a7 --- /dev/null +++ b/src/components/changelog/Version.tsx @@ -0,0 +1,21 @@ +import type { Changelog } from "./changelogs"; + +function Version({ version, changes, title }: Changelog) { + return ( + + + {!!title && {title}} + Version {version}: + + + {changes.map((change) => ( + + {change} + + ))} + + + ); +} + +export default Version; diff --git a/src/components/changelog/changelogs.ts b/src/components/changelog/changelogs.ts new file mode 100644 index 0000000..26afc08 --- /dev/null +++ b/src/components/changelog/changelogs.ts @@ -0,0 +1,48 @@ +export type Changelog = { + version: string; + changes: string[]; + title?: string; +}; +export const changelogs: Changelog[] = [ + { + version: "0.12.0", + title: "Feature: Changelog dialog.", + changes: [ + "Add Shadcn dialog component.", + "Add changelog component and recent app updates.", + ], + }, + { + version: "0.11.2", + changes: [ + "Fix dynamic map tiles url causing duplicate image caching.", + "Fix security policy causing cached map tiles to use way too much storage space.", + "Fix chrome service worker bug causing significant slowdown when ignoring url search params with many images cached.", + ], + }, + { + version: "0.11.1", + changes: [ + "Fix z-index inconsistencies in footer, global alerts, saved stops, and drawer/sheet ui components.", + ], + }, + { + version: "0.11.0", + title: + "Feature: change footer from accordion to mobile friendly drawer component.", + changes: [ + "Add shadcn drawer component.", + "Adjust global muted color style.", + ], + }, + { + version: "0.10.0", + title: "Feature: support installation as progressive web app (PWA).", + changes: [ + "High resolution icons for android and iOS.", + "Cache map tiles for for reduced network bandwidth.", + "Skip animations when changing map location.", + "Adjust zoom level that displays vehicle markers.", + ], + }, +]; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..7a7145f --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 14ec676..b31a0a2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -11,7 +11,7 @@ import Modal from "@/components/Modal"; import useRouteId from "@/hooks/useRouteId"; import MainNav from "@/components/MainNav"; import { - useElementSize, + useResizeObserver, useLocalStorage, useMediaQuery, useWindowSize, @@ -40,6 +40,7 @@ import Link from "next/link"; import dynamic from "next/dynamic"; import { LatLngExpression, LatLngTuple } from "leaflet"; import useRoute from "@/hooks/useRoute"; +import Changelog from "@/components/changelog/Changelog"; const MapContainer = dynamic(() => import("../components/Map"), { ssr: false, @@ -98,10 +99,13 @@ export default function Home() { const [showNewUser, setShowNewUser] = useState( !(!!routeId || !!tripId || !!stopId || !!destId), ); + const [showChangelog, setShowChangelog] = useState(false); const { height: windowHeight } = useWindowSize(); - const [navContainer, { height: navHeight }] = - useElementSize(); + const navContainer = useRef(null); + const { height: navHeight = 0 } = useResizeObserver({ + ref: navContainer, + }); const isMobile = useMediaQuery("(max-width: 1023px)"); const navRef = useRef(null); @@ -356,6 +360,12 @@ export default function Home() { Report an issue + + + setShowChangelog(true)} className="w-full"> + Changelog + + @@ -431,6 +441,8 @@ export default function Home() { /> )} + +
{title}