Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save makarovas/0bb67c640ea172aea2b88c95e49266c4 to your computer and use it in GitHub Desktop.
Save makarovas/0bb67c640ea172aea2b88c95e49266c4 to your computer and use it in GitHub Desktop.
Map component (Typescript, React, Next.js, react-simple-maps )
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