From e56554b4eefa8ed6535c354c1027b8f4a9a01f6c Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Fri, 5 Dec 2025 01:49:53 -0500 Subject: [PATCH] feat: Introduce skeleton UI component for loading states in the drawer, update ESLint configuration, and refactor tRPC timing middleware. --- eslint.config.js | 2 +- package.json | 4 +-- src/components/Drawer.tsx | 50 ++++++++++++++++++++++----------- src/components/Map.tsx | 4 +-- src/components/WelcomeModal.tsx | 2 +- src/components/ui/skeleton.tsx | 15 ++++++++++ src/server/api/trpc.ts | 19 ++++++------- 7 files changed, 62 insertions(+), 34 deletions(-) create mode 100644 src/components/ui/skeleton.tsx diff --git a/eslint.config.js b/eslint.config.js index 18540a3..b507fcb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,7 +9,7 @@ export default tseslint.config( { ignores: [".next"], }, - ...compat.extends("next/core-web-vitals"), + // ...compat.extends("next/core-web-vitals"), { files: ["**/*.ts", "**/*.tsx"], extends: [ diff --git a/package.json b/package.json index 1414252..0e6e63e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dev": "next dev --turbo", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", - "lint": "next lint", + "lint": "eslint .", "lint:fix": "next lint --fix", "preview": "next build && next start", "start": "next start", @@ -64,4 +64,4 @@ "@types/react": "19.2.7", "@types/react-dom": "19.2.3" } -} +} \ No newline at end of file diff --git a/src/components/Drawer.tsx b/src/components/Drawer.tsx index 0a8aaf2..a86c84d 100644 --- a/src/components/Drawer.tsx +++ b/src/components/Drawer.tsx @@ -1,27 +1,38 @@ -import { X, MapPin, Phone, Globe, ExternalLink } from "lucide-react"; +import { X, MapPin, Globe, Phone, Coffee, ExternalLink } from "lucide-react"; import { Card } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { ScrollArea } from "~/components/ui/scroll-area"; import { Separator } from "~/components/ui/separator"; - -interface CoffeeShop { - id: number; - name: string; - description: string; - lat: number; - lng: number; - address: string; - phone: string; - website: string; - image: string; -} +import { Skeleton } from "~/components/ui/skeleton"; +import { useState, useEffect } from "react"; interface DrawerProps { - shop: CoffeeShop | null; + shop: { + id: number; + name: string; + description: string; + image: string; + address: string; + phone: string; + website: string; + lat: number; + lng: number; + } | null; onClose: () => void; } export default function Drawer({ shop, onClose }: DrawerProps) { + const [imageLoading, setImageLoading] = useState(true); + + // Reset loading state when shop changes + useEffect(() => { + if (shop) { + setImageLoading(true); + } + }, [shop]); + + if (!shop) return null; + return (
{/* Header Image */} -
+
+ {imageLoading && ( +
+ + +
+ )}
{shop.name} setImageLoading(false)} style={{ maskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)', WebkitMaskImage: 'linear-gradient(to bottom, black 50%, transparent 100%)' diff --git a/src/components/Map.tsx b/src/components/Map.tsx index 98a63be..0912214 100644 --- a/src/components/Map.tsx +++ b/src/components/Map.tsx @@ -4,8 +4,6 @@ import { MapContainer, TileLayer, Marker } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; import L from 'leaflet'; import { useState, useEffect } from 'react'; -import { renderToStaticMarkup } from 'react-dom/server'; -import { Coffee } from 'lucide-react'; import { useTheme } from "next-themes"; import Navbar from "./Navbar"; import { MapStyleControl } from "./MapStyleControl"; @@ -44,7 +42,7 @@ const Map = ({ shops, onShopSelect }: MapProps) => { return new L.DivIcon({ className: 'custom-icon', html: `
- +
`, iconSize: [32, 32], iconAnchor: [16, 32], diff --git a/src/components/WelcomeModal.tsx b/src/components/WelcomeModal.tsx index 63fb841..661593e 100644 --- a/src/components/WelcomeModal.tsx +++ b/src/components/WelcomeModal.tsx @@ -37,7 +37,7 @@ export function WelcomeModal() {
- Welcome to the Lewisburg Coffee Map + Welcome to the Lewisburg Coffee Map Discover the best coffee spots in Lewisburg, PA. diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..33a231a --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "~/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 4665869..5385e26 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -71,24 +71,21 @@ export const createCallerFactory = t.createCallerFactory; export const createTRPCRouter = t.router; /** - * Middleware for timing procedure execution and adding an artificial delay in development. + * Middleware for timing procedure execution and adding an artificial delay. * - * You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating - * network latency that would occur in production but not in local development. + * You can remove this if you don't want to do it. */ -const timingMiddleware = t.middleware(async ({ next, path }) => { - const start = Date.now(); - +const timingMiddleware = t.middleware(async ({ next }) => { if (t._config.isDev) { - // artificial delay in dev + // artificial delay in dev 100-500ms const waitMs = Math.floor(Math.random() * 400) + 100; await new Promise((resolve) => setTimeout(resolve, waitMs)); } - const result = await next(); - - const end = Date.now(); - + if (t._config.isDev) { + // const durationMs = Date.now() - start; + // console.log(`[TRPC] ${path} took ${durationMs}ms to execute`); + } return result; });