Files
livedash-node/components/Map.tsx
Kaj Kowalski 93fbb44eec feat: comprehensive Biome linting fixes and code quality improvements
Major code quality overhaul addressing 58% of all linting issues:

• Type Safety Improvements:
  - Replace all any types with proper TypeScript interfaces
  - Fix Map component shadowing (renamed to CountryMap)
  - Add comprehensive custom error classes system
  - Enhance API route type safety

• Accessibility Enhancements:
  - Add explicit button types to all interactive elements
  - Implement useId() hooks for form element accessibility
  - Add SVG title attributes for screen readers
  - Fix static element interactions with keyboard handlers

• React Best Practices:
  - Resolve exhaustive dependencies warnings with useCallback
  - Extract nested component definitions to top level
  - Fix array index keys with proper unique identifiers
  - Improve component organization and prop typing

• Code Organization:
  - Automatic import organization and type import optimization
  - Fix unused function parameters and variables
  - Enhanced error handling with structured error responses
  - Improve component reusability and maintainability

Results: 248 → 104 total issues (58% reduction)
- Fixed all critical type safety and security issues
- Enhanced accessibility compliance significantly
- Improved code maintainability and performance
2025-06-29 07:35:45 +02:00

83 lines
2.5 KiB
TypeScript

"use client";
import { CircleMarker, MapContainer, TileLayer, Tooltip } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { getLocalizedCountryName } from "../lib/localization";
interface CountryData {
code: string;
count: number;
coordinates: [number, number];
}
interface MapProps {
countryData: CountryData[];
maxCount: number;
}
const CountryMap = ({ countryData, maxCount }: MapProps) => {
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// Don't render until mounted to avoid hydration mismatch
if (!mounted) {
return <div className="h-full w-full bg-muted animate-pulse rounded-lg" />;
}
const isDark = theme === "dark";
// Use different tile layers based on theme
const tileLayerUrl = isDark
? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png"
: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png";
const tileLayerAttribution = isDark
? '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>';
return (
<MapContainer
center={[30, 0]}
zoom={2}
zoomControl={true}
scrollWheelZoom={false}
style={{ height: "100%", width: "100%", borderRadius: "0.5rem" }}
>
<TileLayer attribution={tileLayerAttribution} url={tileLayerUrl} />
{countryData.map((country) => (
<CircleMarker
key={country.code}
center={country.coordinates}
radius={5 + (country.count / maxCount) * 20}
pathOptions={{
fillColor: "hsl(var(--primary))",
color: "hsl(var(--primary))",
weight: 2,
opacity: 0.9,
fillOpacity: 0.6,
}}
>
<Tooltip>
<div className="p-2 bg-background border border-border rounded-md shadow-md">
<div className="font-medium text-foreground">
{getLocalizedCountryName(country.code)}
</div>
<div className="text-sm text-muted-foreground">
Sessions: {country.count}
</div>
</div>
</Tooltip>
</CircleMarker>
))}
</MapContainer>
);
};
export default CountryMap;