Last active
February 12, 2023 08:06
-
-
Save makarovas/0bb67c640ea172aea2b88c95e49266c4 to your computer and use it in GitHub Desktop.
Map component (Typescript, React, Next.js, react-simple-maps )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {AuthRequired} from 'common/AuthRequired' | |
import {memo, MouseEvent, useCallback, useState} from 'react' | |
import {ComposableMap, Geographies as RSMGeographies, Geography, Marker, ZoomableGroup} from 'react-simple-maps' | |
import {DoughnutChart} from './DoughnutChart' | |
import {useData} from './hooks' | |
import {StatsButton} from './StatsButton' | |
import {Tooltip} from './Tooltip' | |
import {A2CountriesUnion, Country, Geo, TooltipDirection} from './types' | |
const geoUrl = 'https://secret' | |
const markers: Array<{name: string; coordinates: [number, number]; direction: TooltipDirection}> = [ | |
{name: 'Portugal', coordinates: [-13, 43.3], direction: 'left'}, | |
{name: 'Argentina', coordinates: [-65.0, -35.7], direction: 'right'}, | |
{name: 'Uganda', coordinates: [28.7, 2.3], direction: 'left'}, | |
{name: 'Kenya', coordinates: [38.8, 4.1], direction: 'right'}, | |
{name: 'India', coordinates: [75.8, 30.5], direction: 'top'}, | |
{name: 'Chile', coordinates: [-72.8, -20.9], direction: 'left'}, | |
{name: 'Spain', coordinates: [-4, 38.0], direction: 'right'}, | |
] | |
export const GeographiesComponent = memo(function Geographies() { | |
const [selected, setSelected] = useState<Country | null>(null) | |
const {topCountries, otherCountries, activeOrbsSum} = useData() | |
const fillGeo = useCallback( | |
(geo: Geo) => { | |
const isCountryInList = (id: A2CountriesUnion) => geo.properties['Alpha-2'] === id | |
const isTopCountryWithOrbs = topCountries.some((country) => isCountryInList(country.id)) | |
const isOtherCountryWithOrbs = otherCountries.countries.some((country) => isCountryInList(country.id)) | |
if (isTopCountryWithOrbs && selected?.id === geo.properties['Alpha-2']) { | |
return selected.color | |
} | |
if (isOtherCountryWithOrbs && selected?.id === geo.properties['Alpha-2']) { | |
return otherCountries.color | |
} | |
if (topCountries.some((country) => country.id !== selected?.id && country.id === geo.id)) { | |
return otherCountries.color | |
} | |
if (isTopCountryWithOrbs || isOtherCountryWithOrbs) { | |
return '#000000' | |
} | |
return '#D9D9D9' | |
}, | |
[otherCountries.color, otherCountries.countries, selected, topCountries], | |
) | |
const findCountry = useCallback( | |
(id: A2CountriesUnion) => { | |
if (topCountries.some((country) => country.id === id)) { | |
return topCountries.find((country) => country.id === id) | |
} | |
return otherCountries.countries.find((country) => country.id === id) | |
}, | |
[otherCountries, topCountries], | |
) | |
const selectCountry = useCallback( | |
(item?: Country) => { | |
if (!item) { | |
return setSelected(null) | |
} | |
if (item.id === selected?.id) { | |
return setSelected(null) | |
} | |
setSelected(item) | |
}, | |
[selected], | |
) | |
const onMapClick = useCallback((event: MouseEvent<SVGElement>) => { | |
const target = event.target as SVGElement | |
if (target.localName !== 'rect') { | |
return | |
} | |
setSelected(null) | |
}, []) | |
return ( | |
<AuthRequired> | |
<div className="grid justify-items-center relative"> | |
<ComposableMap className="max-h-screen w-full" onClick={onMapClick} projection="geoMercator"> | |
<ZoomableGroup zoom={0.7} center={[20, 10]}> | |
<RSMGeographies geography={geoUrl}> | |
{({geographies}: {geographies: Array<Geo>}) => | |
geographies.map((geo) => ( | |
<Geography | |
key={geo.rsmKey} | |
fill={fillGeo(geo)} | |
className="outline-none transition-colors" | |
strokeWidth={0.7} | |
stroke="#7A8A9526" | |
geography={geo} | |
onClick={() => selectCountry(findCountry(geo.properties['Alpha-2'] as A2CountriesUnion))} | |
/> | |
)) | |
} | |
</RSMGeographies> | |
{markers.map(({name, coordinates, direction}: typeof markers[0]) => { | |
let data | |
const foundTopCountry = topCountries.find((item) => item.name === name) | |
const foundOtherCountry = otherCountries.countries.find((item) => item.name === name) | |
data = foundTopCountry ?? foundOtherCountry | |
if (!data) { | |
return | |
} | |
const isSelected = Array.isArray(selected) ? false : selected?.name === data.name | |
return ( | |
<Marker z={0} key={name} coordinates={coordinates}> | |
<Tooltip isOpened={isSelected} data={data} to={direction} setSelected={setSelected} /> | |
</Marker> | |
) | |
})} | |
</ZoomableGroup> | |
</ComposableMap> | |
<h1 className="absolute top-6 left-8 font-rubik font-semibold text-000000 text-32">Geographies</h1> | |
{!otherCountries.countries.some((country) => country.id === selected?.id) && ( | |
<div className="absolute bottom-32 left-8 grid gap-y-2.5 pointer-events-none"> | |
<span className="text-[160px] leading-none font-sora font-semibold text-000000"> | |
{!selected && activeOrbsSum} | |
{selected && selected.orbData?.count} | |
</span> | |
<p className="text-000000/40 font-rubik font-medium uppercase"> | |
{!selected && 'Active orbs in the field and 16 countries worldcoin launched in'} | |
{selected && ( | |
<span> | |
Number of active orbs in the field <span className="text-000000">{selected.name}</span> | |
</span> | |
)} | |
</p> | |
</div> | |
)} | |
<div className="absolute bottom-32 right-8 bg-ffffff rounded-xl p-3 shadow-card w-[300px]"> | |
<DoughnutChart data={topCountries} setSelected={setSelected} className="mx-auto mb-12 mt-4" /> | |
<div className="grid gap-y-1 px-3 pb-3"> | |
<p className="font-sora font-semibold">Sign-ups per country</p> | |
<span className="text-667085 text-12">total / last week per country</span> | |
</div> | |
<hr className="mx-3" /> | |
<div className="mt-1"> | |
{topCountries.map((country) => ( | |
<StatsButton | |
key={`country-button-${country.id}`} | |
name={country.name} | |
totalSignups={country.totalSignups} | |
lastWeekSignups={country.lastWeek} | |
onClick={() => selectCountry(country)} | |
selected={selected} | |
color={country.color ?? '#FFFFFF'} | |
className="px-3" | |
/> | |
))} | |
</div> | |
</div> | |
</div> | |
</AuthRequired> | |
) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment