Skip to content

Commit b37f15b

Browse files
jdamcdclaude
andcommitted
Centre map on home country and fix map shading
- Fly to home country on load and when selection changes - Add country centroids for all countries - Fix duplicate country highlight shading via worldview filter - Add GitHub source link to footer - Fix country ordering bug Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e28d230 commit b37f15b

4 files changed

Lines changed: 90 additions & 17 deletions

File tree

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# tripm.app
22

3-
A visual travel history tracker that extracts trips from your calendar.
3+
A visual travel history based on events in your calendar.
44

55
![tripm.app](screenshot.png)
66

@@ -12,10 +12,9 @@ A visual travel history tracker that extracts trips from your calendar.
1212
- Hotel and accommodation bookings
1313
- Multi-day events with country or city names
1414
- Travel keywords (flight, hotel, trip, etc.)
15-
- **Interactive world map**: View visited countries on an interactive world map
15+
- **Map**: View visited countries on an interactive world map
1616
- **Trip management**: Add, edit, and delete trips manually
17-
- **Date filtering**: Filter trips by date range
18-
- **Home country**: Set a home country to exclude from visit counts
17+
- **Filters**: Filter trips by date and exclude events from your home country
1918
- **Export/import**: Back up and restore your trip history
2019

2120
## Local development
@@ -53,7 +52,6 @@ npm run build
5352
- Tailwind CSS v4
5453
- MapBox GL JS via react-map-gl
5554
- ical.js for calendar parsing
56-
- Vitest for testing
5755

5856
## License
5957

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ function App() {
291291
{/* Footer */}
292292
<footer className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 px-4 py-3 mt-auto">
293293
<div className="max-w-7xl mx-auto text-center text-sm text-gray-500 dark:text-gray-400">
294-
All data is processed locally in your browser. Use import / export to back up your trip history.
294+
All data is processed locally in your browser. Use import / export to back up your trip history. <a href="https://github.com/jdamcd/trip-map" target="_blank" rel="noopener noreferrer" className="hover:text-gray-700 dark:hover:text-gray-300 underline">Source code on GitHub</a>.
295295
</div>
296296
</footer>
297297
</div>

src/components/WorldMap.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useCallback, useEffect, useMemo, useState } from 'react';
1+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import { Map as MapGL, Layer, Source, Popup } from 'react-map-gl/mapbox';
33
import type { MapLayerMouseEvent } from 'react-map-gl/mapbox';
4+
import type { MapRef } from '@vis.gl/react-mapbox';
45
import type { CountryVisit, VisitEntry } from '../types';
5-
import { countries } from '../data/countries';
6+
import { countries, countryCoordinates } from '../data/countries';
67
import { format } from 'date-fns';
78
import 'mapbox-gl/dist/mapbox-gl.css';
89

@@ -35,6 +36,7 @@ function useDarkMode() {
3536

3637
export function WorldMap({ visits, homeCountry, onCountryClick }: WorldMapProps) {
3738
const isDark = useDarkMode();
39+
const mapRef = useRef<MapRef>(null);
3840
const [hoverInfo, setHoverInfo] = useState<{
3941
longitude: number;
4042
latitude: number;
@@ -62,7 +64,12 @@ export function WorldMap({ visits, homeCountry, onCountryClick }: WorldMapProps)
6264
}, [visits, homeCountry]);
6365

6466
const filter = useMemo(
65-
() => ['in', ['get', 'iso_3166_1'], ['literal', highlightedCountryCodes]],
67+
() => [
68+
'all',
69+
['in', ['get', 'iso_3166_1'], ['literal', highlightedCountryCodes]],
70+
// Filter by worldview to avoid duplicate polygons causing opacity stacking
71+
['any', ['==', ['get', 'worldview'], 'all'], ['in', 'US', ['get', 'worldview']]],
72+
],
6673
[highlightedCountryCodes]
6774
);
6875

@@ -122,17 +129,30 @@ export function WorldMap({ visits, homeCountry, onCountryClick }: WorldMapProps)
122129
[onCountryClick, visitsByCode]
123130
);
124131

132+
useEffect(() => {
133+
if (!homeCountry || !mapRef.current) return;
134+
const coords = countryCoordinates[homeCountry];
135+
if (!coords) return;
136+
mapRef.current.flyTo({ center: coords, zoom: 3, duration: 1000 });
137+
}, [homeCountry]);
138+
139+
const initialViewState = useMemo(() => {
140+
if (homeCountry) {
141+
const coords = countryCoordinates[homeCountry];
142+
if (coords) return { longitude: coords[0], latitude: coords[1], zoom: 3 };
143+
}
144+
return { longitude: 0, latitude: 20, zoom: 1.75 };
145+
// eslint-disable-next-line react-hooks/exhaustive-deps
146+
}, []);
147+
125148
const mapStyle = isDark
126149
? 'mapbox://styles/mapbox/dark-v11'
127150
: 'mapbox://styles/mapbox/light-v11';
128151

129152
return (
130153
<MapGL
131-
initialViewState={{
132-
longitude: 0,
133-
latitude: 20,
134-
zoom: 1.75,
135-
}}
154+
ref={mapRef}
155+
initialViewState={initialViewState}
136156
minZoom={1.75}
137157
maxZoom={5}
138158
style={{ width: '100%', height: '100%' }}

src/data/countries.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export const countries: Country[] = [
7878
{ code: 'GY', name: 'Guyana' },
7979
{ code: 'HT', name: 'Haiti' },
8080
{ code: 'HN', name: 'Honduras' },
81+
{ code: 'HK', name: 'Hong Kong' },
8182
{ code: 'HU', name: 'Hungary' },
8283
{ code: 'IS', name: 'Iceland' },
8384
{ code: 'IN', name: 'India' },
@@ -106,6 +107,7 @@ export const countries: Country[] = [
106107
{ code: 'LI', name: 'Liechtenstein' },
107108
{ code: 'LT', name: 'Lithuania' },
108109
{ code: 'LU', name: 'Luxembourg' },
110+
{ code: 'MO', name: 'Macau' },
109111
{ code: 'MG', name: 'Madagascar' },
110112
{ code: 'MW', name: 'Malawi' },
111113
{ code: 'MY', name: 'Malaysia' },
@@ -145,6 +147,7 @@ export const countries: Country[] = [
145147
{ code: 'PH', name: 'Philippines' },
146148
{ code: 'PL', name: 'Poland' },
147149
{ code: 'PT', name: 'Portugal' },
150+
{ code: 'PR', name: 'Puerto Rico' },
148151
{ code: 'QA', name: 'Qatar' },
149152
{ code: 'RO', name: 'Romania' },
150153
{ code: 'RU', name: 'Russia' },
@@ -200,11 +203,63 @@ export const countries: Country[] = [
200203
{ code: 'YE', name: 'Yemen' },
201204
{ code: 'ZM', name: 'Zambia' },
202205
{ code: 'ZW', name: 'Zimbabwe' },
203-
{ code: 'HK', name: 'Hong Kong' },
204-
{ code: 'MO', name: 'Macau' },
205-
{ code: 'PR', name: 'Puerto Rico' },
206206
];
207207

208+
// Country centroids [longitude, latitude] for map centering
209+
export const countryCoordinates: Record<string, [number, number]> = {
210+
'AF': [65, 33], 'AL': [20, 41], 'DZ': [3, 28], 'AD': [1.5, 42.5],
211+
'AO': [18, -12], 'AG': [-61.8, 17.1], 'AR': [-64, -34], 'AM': [45, 40],
212+
'AU': [134, -25], 'AT': [14, 47.5], 'AZ': [47.5, 40.5], 'BS': [-77.5, 24],
213+
'BH': [50.5, 26], 'BD': [90, 24], 'BB': [-59.6, 13.2], 'BY': [28, 54],
214+
'BE': [4.5, 50.5], 'BZ': [-88.7, 17.3], 'BJ': [2.5, 9.5], 'BT': [91, 27.5],
215+
'BO': [-65, -17], 'BA': [18, 44], 'BW': [24, -22], 'BR': [-55, -10],
216+
'BN': [115, 4.5], 'BG': [25, 43], 'BF': [-1.5, 12], 'BI': [29.9, -3.4],
217+
'CV': [-23.6, 16], 'KH': [105, 12], 'CM': [12, 6], 'CA': [-95, 60],
218+
'CF': [19, 7], 'TD': [18, 15], 'CL': [-72, -35], 'CN': [105, 35],
219+
'CO': [-72, 4], 'KM': [43.8, -12], 'CG': [15, -1], 'CD': [25, -3],
220+
'CR': [-84, 9.8], 'CI': [-5.5, 6.5], 'HR': [16, 45.5], 'CU': [-79, 22],
221+
'CY': [33, 35], 'CZ': [15, 50], 'DK': [10, 56], 'DJ': [43, 11.5],
222+
'DM': [-61.4, 15.5], 'DO': [-76, 19], 'EC': [-78, -2], 'EG': [30, 27],
223+
'SV': [-88.8, 13.8], 'GQ': [11, 2], 'ER': [38, 15], 'EE': [26, 59],
224+
'SZ': [31.8, -26.5], 'ET': [40, 9], 'FJ': [178, -18], 'FI': [26, 64],
225+
'FR': [2, 46], 'GA': [11.5, -0.5], 'GM': [-15, 13.4], 'GE': [44, 42],
226+
'DE': [10, 51], 'GH': [-2, 8], 'GR': [22, 39], 'GD': [-61.7, 12.1],
227+
'GT': [-90.2, 15.5], 'GN': [-11, 11], 'GW': [-14.7, 12], 'GY': [-59, 5],
228+
'HT': [-72.3, 19], 'HN': [-86.5, 14], 'HU': [19, 47], 'IS': [-18, 65],
229+
'IN': [77, 20], 'ID': [118, -2], 'IR': [53, 32], 'IQ': [44, 33],
230+
'IE': [-8, 53.5], 'IL': [35, 32], 'IT': [12, 42], 'JM': [-77.5, 18.2],
231+
'JP': [138, 36], 'JO': [36, 31], 'KZ': [68, 48], 'KE': [38, -1],
232+
'KI': [173, 1.5], 'KP': [127, 40], 'KR': [128, 36], 'KW': [48, 29.5],
233+
'KG': [75, 41], 'LA': [105, 18], 'LV': [25, 57], 'LB': [36, 34],
234+
'LS': [29, -29.5], 'LR': [-10, 6], 'LY': [17, 25], 'LI': [9.5, 47.1],
235+
'LT': [24, 56], 'LU': [6, 49.5], 'MG': [47, -20], 'MW': [35, -13.5],
236+
'MY': [109, 4], 'MV': [73.5, 4], 'ML': [-2, 17], 'MT': [14.5, 35.5],
237+
'MH': [171, 8], 'MR': [-11, 18], 'MU': [57.5, -20.2], 'MX': [-102, 24],
238+
'FM': [153, 7], 'MD': [29, 47], 'MC': [7.4, 43.7], 'MN': [105, 46],
239+
'ME': [19, 42.5], 'MA': [-5, 32], 'MZ': [35, -18], 'MM': [96, 20],
240+
'NA': [18, -22], 'NR': [166.9, -0.5], 'NP': [84, 28], 'NL': [5.5, 52.5],
241+
'NZ': [174, -41], 'NI': [-85.8, 12.9], 'NE': [8, 16], 'NG': [8, 9],
242+
'MK': [22, 41.5], 'NO': [10, 62], 'OM': [58, 22], 'PK': [69, 30],
243+
'PW': [134.5, 7.5], 'PS': [35.3, 31.9], 'PA': [-80, 9], 'PG': [147, -6],
244+
'PY': [-58, -23], 'PE': [-76, -10], 'PH': [122, 13], 'PL': [20, 52],
245+
'PT': [-8, 39.5], 'QA': [51.2, 25.5], 'RO': [25, 46], 'RU': [100, 60],
246+
'RW': [29.9, -2], 'KN': [-62.7, 17.3], 'LC': [-61, 13.9],
247+
'VC': [-61.2, 13.2], 'WS': [-171.8, -14], 'SM': [12.4, 43.9],
248+
'ST': [6.7, 0.2], 'SA': [45, 24], 'SN': [-14, 14], 'RS': [21, 44],
249+
'SC': [55.5, -4.7], 'SL': [-11.8, 8.5], 'SG': [103.8, 1.3],
250+
'SK': [19, 48.5], 'SI': [15, 46], 'SB': [161, -9], 'SO': [46, 5.5],
251+
'ZA': [25, -29], 'SS': [32, 7], 'ES': [-4, 40], 'LK': [80, 8],
252+
'SD': [30, 12], 'SR': [-56, 4], 'SE': [15, 62], 'CH': [8, 47],
253+
'SY': [38, 35], 'TW': [121, 24], 'TJ': [71, 39], 'TZ': [35, -6],
254+
'TH': [101, 15], 'TL': [126, -8.5], 'TG': [1.2, 9.5], 'TO': [-175, -20.5],
255+
'TT': [-61.2, 10.5], 'TN': [9, 34], 'TR': [35, 39], 'TM': [60, 40],
256+
'TV': [179, -8], 'UG': [32, 1], 'UA': [32, 49], 'AE': [54, 24],
257+
'GB': [-2, 54], 'US': [-97, 38], 'UY': [-56, -33], 'UZ': [64, 41],
258+
'VU': [167, -16], 'VA': [12.4, 41.9], 'VE': [-66, 8], 'VN': [108, 16],
259+
'YE': [48, 15], 'ZM': [28, -14], 'ZW': [30, -19], 'HK': [114.2, 22.3],
260+
'MO': [113.5, 22.2], 'PR': [-66, 18.2],
261+
};
262+
208263
export const countryByCode: Record<string, Country> = Object.fromEntries(
209264
countries.map((c) => [c.code, c])
210265
);

0 commit comments

Comments
 (0)