├── .eslintignore
├── .prettierrc.json
├── src
├── assets
│ ├── images
│ │ ├── Graphs.png
│ │ ├── logo.png
│ │ ├── logos
│ │ │ ├── cable.png
│ │ │ ├── star.jpg
│ │ │ ├── nation.png
│ │ │ ├── premium.png
│ │ │ ├── womanng.png
│ │ │ ├── business.png
│ │ │ ├── guardian.png
│ │ │ ├── logowhite.png
│ │ │ ├── luftdaten.jpg
│ │ │ ├── partners.png
│ │ │ ├── dailynation.png
│ │ │ ├── sensorsLogo.png
│ │ │ ├── cfafrica_gray.png
│ │ │ ├── cfafrica_white.png
│ │ │ ├── logoExperimental.png
│ │ │ ├── innovateafrica_gray.jpg
│ │ │ └── innovateafrica_white.png
│ │ ├── team
│ │ │ ├── chege.png
│ │ │ └── kirah.jpeg
│ │ ├── button
│ │ │ ├── airbtn.png
│ │ │ ├── soundbtn.png
│ │ │ ├── waterbtn.png
│ │ │ └── radiationbtn.png
│ │ ├── Showcase
│ │ │ ├── london.jpg
│ │ │ ├── sensor.png
│ │ │ ├── toxic.jpg
│ │ │ ├── children.jpg
│ │ │ ├── hindustan.png
│ │ │ ├── airpollution.jpg
│ │ │ ├── airquality.png
│ │ │ └── sensorMedium.png
│ │ ├── healthimpacticon.png
│ │ ├── icons
│ │ │ ├── cropdamage.png
│ │ │ ├── snowmelt.png
│ │ │ ├── AmbientIcon.png
│ │ │ ├── HouseholdIcon.png
│ │ │ └── Risingtemperature.png
│ │ ├── sensors
│ │ │ ├── sensor1.jpg
│ │ │ ├── sensor2.jpg
│ │ │ ├── sensor3.jpg
│ │ │ └── sensor4.jpg
│ │ ├── climateimpacticon.png
│ │ ├── partners
│ │ │ ├── data4sdg.jpg
│ │ │ ├── liquidtelcom.png
│ │ │ ├── germanCoopLogo.png
│ │ │ ├── partnershipsdg.png
│ │ │ └── worldbankgroup.png
│ │ └── background
│ │ │ ├── bglanding.jpg
│ │ │ ├── bgstories.jpg
│ │ │ └── bgsupport.jpg
│ └── css
│ │ ├── index.css
│ │ └── App.css
├── components
│ ├── Embeds
│ │ ├── index.js
│ │ ├── AirGraph.js
│ │ ├── AirMap.js
│ │ └── AirGauge.js
│ ├── Filter
│ │ ├── index.js
│ │ └── Select.js
│ ├── City
│ │ ├── Header
│ │ │ ├── CityMenuBar.js
│ │ │ └── CityHeader.js
│ │ ├── HostSensors
│ │ │ ├── ShareButton
│ │ │ │ ├── Embed.js
│ │ │ │ ├── SocialMediaButtons.js
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ ├── HostSensorButton.js
│ │ │ ├── HostSensorButtons.js
│ │ │ └── HostCard.js
│ │ ├── SensorsQualityStats
│ │ │ ├── index.js
│ │ │ └── StatsSummary.js
│ │ └── AQIndex.js
│ ├── ScrollToTop.js
│ ├── Link
│ │ ├── NextComposed.js
│ │ ├── Button.js
│ │ └── index.js
│ ├── DocumentHead
│ │ ├── index.js
│ │ └── PageHeads.js
│ ├── Header
│ │ ├── JumboContent
│ │ │ ├── AirCityHeaderContent
│ │ │ │ ├── CityGauge.js
│ │ │ │ ├── index.js
│ │ │ │ ├── NeedlePointer.js
│ │ │ │ └── DigitalGauge.js
│ │ │ ├── HealthClimateContent.js
│ │ │ ├── JoinHeaderContent.js
│ │ │ ├── AboutHeaderContent.js
│ │ │ ├── DataArchivesHeaderContent.js
│ │ │ ├── AirHeaderContent.js
│ │ │ └── HardwareHeaderContent.js
│ │ └── MenuBar.js
│ ├── HealthClimate
│ │ ├── HealthAndClimateHeader.js
│ │ ├── PollutionBurden.js
│ │ ├── HealthAndDiseaseBurden.js
│ │ ├── ImpactCards.js
│ │ ├── PollutionSource.js
│ │ └── HealthAndClimateImpact.js
│ ├── Navigation.js
│ ├── Air
│ │ ├── AirHeader.js
│ │ ├── Gauge.js
│ │ ├── HealthEffects.js
│ │ ├── Issues.js
│ │ └── GaugeChart.js
│ ├── JoinNetwork
│ │ └── JoinHeader.js
│ ├── Loading.js
│ ├── DataArchives
│ │ └── DataArchivesHeader.js
│ ├── About
│ │ ├── Header.js
│ │ ├── Air
│ │ │ └── Header.js
│ │ ├── AboutContent
│ │ │ └── Content.js
│ │ └── Stories.js
│ ├── SensorMap
│ │ ├── IframeComponent.js
│ │ └── index.js
│ ├── Showcase
│ │ ├── index.js
│ │ ├── StoryCard.js
│ │ └── StoryList.js
│ ├── AfricaMap
│ │ ├── IframeComponent.js
│ │ └── index.js
│ ├── SensorsInfo
│ │ └── HardwareHeader.js
│ ├── IconLogo.js
│ ├── Logo.js
│ ├── Landing
│ │ └── Hero.js
│ ├── Average
│ │ └── index.js
│ ├── PartnerLogos.js
│ ├── Hambuger
│ │ ├── MenuButton.js
│ │ └── HambugerMenu.js
│ ├── TimeSeries
│ │ └── index.js
│ ├── Ticker
│ │ └── Status.js
│ ├── Favicon.js
│ ├── Email.js
│ ├── Chart
│ │ └── index.js
│ ├── Login.js
│ ├── SocialMedia.js
│ └── Insights.js
├── utils.js
├── lib
│ ├── gtag.js
│ └── index.js
├── pages
│ ├── api
│ │ ├── nodes
│ │ │ └── index.js
│ │ └── auth
│ │ │ └── [...nextauth].js
│ ├── index.js
│ ├── dashboard
│ │ └── docs.js
│ ├── _app.js
│ ├── _document.js
│ └── 404.js
├── config.js
└── theme.js
├── public
├── favicons
│ ├── black
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── mstile-150x150.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── safari-pinned-tab.svg
│ ├── blue
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── mstile-150x150.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── safari-pinned-tab.svg
│ ├── green
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── mstile-150x150.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── safari-pinned-tab.svg
│ ├── orange
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── mstile-150x150.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── safari-pinned-tab.svg
│ └── purple
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── mstile-150x150.png
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── browserconfig.xml
│ │ ├── manifest.json
│ │ └── safari-pinned-tab.svg
└── manifest.json
├── jsconfig.json
├── .babelrc
├── .github
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .eslintrc.json
├── next.config.js
├── README.md
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/serviceWorker.js
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/images/Graphs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Graphs.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logo.png
--------------------------------------------------------------------------------
/public/favicons/black/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/blue/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/green/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/favicon.ico
--------------------------------------------------------------------------------
/src/assets/images/logos/cable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/cable.png
--------------------------------------------------------------------------------
/src/assets/images/logos/star.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/star.jpg
--------------------------------------------------------------------------------
/src/assets/images/team/chege.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/team/chege.png
--------------------------------------------------------------------------------
/src/assets/images/team/kirah.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/team/kirah.jpeg
--------------------------------------------------------------------------------
/public/favicons/orange/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/purple/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/favicon.ico
--------------------------------------------------------------------------------
/src/assets/images/button/airbtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/button/airbtn.png
--------------------------------------------------------------------------------
/src/assets/images/logos/nation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/nation.png
--------------------------------------------------------------------------------
/src/assets/images/logos/premium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/premium.png
--------------------------------------------------------------------------------
/src/assets/images/logos/womanng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/womanng.png
--------------------------------------------------------------------------------
/public/favicons/blue/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/blue/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/images/Showcase/london.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/london.jpg
--------------------------------------------------------------------------------
/src/assets/images/Showcase/sensor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/sensor.png
--------------------------------------------------------------------------------
/src/assets/images/Showcase/toxic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/toxic.jpg
--------------------------------------------------------------------------------
/src/assets/images/button/soundbtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/button/soundbtn.png
--------------------------------------------------------------------------------
/src/assets/images/button/waterbtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/button/waterbtn.png
--------------------------------------------------------------------------------
/src/assets/images/healthimpacticon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/healthimpacticon.png
--------------------------------------------------------------------------------
/src/assets/images/icons/cropdamage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/icons/cropdamage.png
--------------------------------------------------------------------------------
/src/assets/images/icons/snowmelt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/icons/snowmelt.png
--------------------------------------------------------------------------------
/src/assets/images/logos/business.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/business.png
--------------------------------------------------------------------------------
/src/assets/images/logos/guardian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/guardian.png
--------------------------------------------------------------------------------
/src/assets/images/logos/logowhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/logowhite.png
--------------------------------------------------------------------------------
/src/assets/images/logos/luftdaten.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/luftdaten.jpg
--------------------------------------------------------------------------------
/src/assets/images/logos/partners.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/partners.png
--------------------------------------------------------------------------------
/src/assets/images/sensors/sensor1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/sensors/sensor1.jpg
--------------------------------------------------------------------------------
/src/assets/images/sensors/sensor2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/sensors/sensor2.jpg
--------------------------------------------------------------------------------
/src/assets/images/sensors/sensor3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/sensors/sensor3.jpg
--------------------------------------------------------------------------------
/src/assets/images/sensors/sensor4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/sensors/sensor4.jpg
--------------------------------------------------------------------------------
/public/favicons/black/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/black/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/black/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicons/blue/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicons/green/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/green/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/green/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicons/orange/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/orange/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/purple/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/purple/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/images/Showcase/children.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/children.jpg
--------------------------------------------------------------------------------
/src/assets/images/Showcase/hindustan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/hindustan.png
--------------------------------------------------------------------------------
/src/assets/images/climateimpacticon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/climateimpacticon.png
--------------------------------------------------------------------------------
/src/assets/images/icons/AmbientIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/icons/AmbientIcon.png
--------------------------------------------------------------------------------
/src/assets/images/logos/dailynation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/dailynation.png
--------------------------------------------------------------------------------
/src/assets/images/logos/sensorsLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/sensorsLogo.png
--------------------------------------------------------------------------------
/src/assets/images/partners/data4sdg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/partners/data4sdg.jpg
--------------------------------------------------------------------------------
/public/favicons/black/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/blue/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/green/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/orange/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/orange/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicons/purple/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/purple/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/mstile-150x150.png
--------------------------------------------------------------------------------
/src/assets/images/Showcase/airpollution.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/airpollution.jpg
--------------------------------------------------------------------------------
/src/assets/images/Showcase/airquality.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/airquality.png
--------------------------------------------------------------------------------
/src/assets/images/Showcase/sensorMedium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/Showcase/sensorMedium.png
--------------------------------------------------------------------------------
/src/assets/images/background/bglanding.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/background/bglanding.jpg
--------------------------------------------------------------------------------
/src/assets/images/background/bgstories.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/background/bgstories.jpg
--------------------------------------------------------------------------------
/src/assets/images/background/bgsupport.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/background/bgsupport.jpg
--------------------------------------------------------------------------------
/src/assets/images/button/radiationbtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/button/radiationbtn.png
--------------------------------------------------------------------------------
/src/assets/images/icons/HouseholdIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/icons/HouseholdIcon.png
--------------------------------------------------------------------------------
/src/assets/images/logos/cfafrica_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/cfafrica_gray.png
--------------------------------------------------------------------------------
/src/assets/images/logos/cfafrica_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/cfafrica_white.png
--------------------------------------------------------------------------------
/src/assets/images/partners/liquidtelcom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/partners/liquidtelcom.png
--------------------------------------------------------------------------------
/src/assets/images/icons/Risingtemperature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/icons/Risingtemperature.png
--------------------------------------------------------------------------------
/src/assets/images/logos/logoExperimental.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/logoExperimental.png
--------------------------------------------------------------------------------
/src/assets/images/partners/germanCoopLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/partners/germanCoopLogo.png
--------------------------------------------------------------------------------
/src/assets/images/partners/partnershipsdg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/partners/partnershipsdg.png
--------------------------------------------------------------------------------
/src/assets/images/partners/worldbankgroup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/partners/worldbankgroup.png
--------------------------------------------------------------------------------
/public/favicons/black/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/black/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/black/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/blue/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/blue/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/blue/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/green/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/green/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/green/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/assets/images/logos/innovateafrica_gray.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/innovateafrica_gray.jpg
--------------------------------------------------------------------------------
/src/assets/images/logos/innovateafrica_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/src/assets/images/logos/innovateafrica_white.png
--------------------------------------------------------------------------------
/public/favicons/orange/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/orange/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/orange/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/purple/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/purple/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeForAfrica/Data4SDGs-AQ-Dashboard/main/public/favicons/purple/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/components/Embeds/index.js:
--------------------------------------------------------------------------------
1 | import AirGauge from 'components/Embeds/AirGauge';
2 | import AirMap from 'components/Embeds/AirMap';
3 | import AirGraph from 'components/Embeds/AirGraph';
4 |
5 | export { AirMap, AirGraph, AirGauge };
6 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export default function getRandomColor() {
2 | // generates a random hex color '0xFFFFFF<<0' padds the generated number with zeros
3 | return `#${((Math.random() * 0xffffff) << 0).toString(16)}`; // eslint-disable-line no-bitwise
4 | }
5 |
--------------------------------------------------------------------------------
/src/lib/gtag.js:
--------------------------------------------------------------------------------
1 | export const GA_TRACKING_ID = 'G-RJB1E9ZL39';
2 |
3 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages
4 | export const pageview = (url) => {
5 | window.gtag('config', GA_TRACKING_ID, {
6 | page_path: url,
7 | });
8 | };
9 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "*": ["*"],
6 | "api/*": ["src/api/*"],
7 | "assets/*": ["src/assets/*"],
8 | "components/*": ["src/components/*"],
9 | "pages/*": ["src/pages/*"],
10 | "lib/*": ["src/lib/*"]
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/Filter/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Select from './Select';
3 |
4 | export default function Filter({ onChange }) {
5 | const handleChange = (value) => {
6 | onChange(value);
7 | };
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/api/nodes/index.js:
--------------------------------------------------------------------------------
1 | import { fetchAllNodes } from 'api';
2 |
3 | export default async function handler(req, res) {
4 | try {
5 | const data = await fetchAllNodes('https://api.sensors.africa/v1/node');
6 | return res.status(200).json(data);
7 | } catch (err) {
8 | return res.status(500).json(err);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/public/favicons/blue/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #4972b8
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicons/black/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #424143
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicons/green/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #2fb56b
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicons/orange/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #F57C00
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | export function formatDateTime(timestamp) {
2 | const date = new Date(timestamp).toDateString().split(' ').slice(1).join(' ');
3 | const time = new Date(timestamp).toLocaleTimeString([], {
4 | hour: '2-digit',
5 | minute: '2-digit',
6 | });
7 | return { date, time };
8 | }
9 |
10 | export default formatDateTime;
11 |
--------------------------------------------------------------------------------
/public/favicons/purple/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #9f00a7
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/assets/css/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | padding: 0;
3 | margin: 0;
4 | min-height: 100%;
5 | }
6 |
7 | body {
8 | padding: 0;
9 | margin: 0;
10 | min-height: 100%;
11 | background-color: white;
12 | /**overflow-x: hidden;**/
13 | }
14 |
15 | h1,
16 | h2,
17 | h3,
18 | h4,
19 | h5,
20 | h6,
21 | p {
22 | font-family: 'Montserrat', sans-serif;
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Embeds/AirGraph.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import QualityStatsGraph from 'components/City/QualityStatsGraph';
5 |
6 | function AirGraph({ data }) {
7 | return ;
8 | }
9 |
10 | AirGraph.propTypes = {
11 | data: PropTypes.shape({}).isRequired,
12 | };
13 |
14 | export default AirGraph;
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": [
4 | [
5 | "module-resolver",
6 | {
7 | "root": ["./src"],
8 | "alias": {
9 | "api": "./src/api",
10 | "assets": "./src/assets",
11 | "components": "./src/components",
12 | "pages": "./src/pages",
13 | "lib": "./src/lib"
14 | }
15 | }
16 | ]
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "sensors.AFRICA",
3 | "name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "favicons/black/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "favicons/black/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "start_url": "./index.html",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff"
20 | }
21 |
--------------------------------------------------------------------------------
/public/favicons/blue/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensors.AFRICA",
3 | "short_name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "/favicons/blue/android-chrome-192x192.png?v=8wB2COTulV",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/blue/android-chrome-512x512.png?v=8wB2COTulV",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#4972b8",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/favicons/black/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensors.AFRICA",
3 | "short_name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "/favicons/black/android-chrome-192x192.png?v=Ewqar9pC28",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/black/android-chrome-512x512.png?v=Ewqar9pC28",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#424143",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/favicons/purple/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensors.AFRICA",
3 | "short_name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "/favicons/purple/android-chrome-192x192.png?v=ng9qR2KowR",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/purple/android-chrome-512x512.png?v=ng9qR2KowR",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/favicons/green/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensors.AFRICA | Air",
3 | "short_name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "/favicons/green/android-chrome-192x192.png?v=78bppvKdQd",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/green/android-chrome-512x512.png?v=78bppvKdQd",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#2fb56b",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/favicons/orange/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sensors.AFRICA | Radiation",
3 | "short_name": "sensors.AFRICA",
4 | "icons": [
5 | {
6 | "src": "/favicons/orange/android-chrome-192x192.png?v=WxbPiUQG57",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/orange/android-chrome-512x512.png?v=WxbPiUQG57",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#F57C00",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Hero from 'components/Landing/Hero';
3 | import { providers, useSession } from 'next-auth/client';
4 | import Router from 'next/router';
5 |
6 | function Home(props) {
7 | const [session] = useSession();
8 | if (session) {
9 | Router.push('/dashboard');
10 | }
11 | return (
12 | <>
13 |
14 | >
15 | );
16 | }
17 |
18 | export async function getStaticProps(context) {
19 | return { props: { providers: await providers(context) } };
20 | }
21 |
22 | export default Home;
23 |
--------------------------------------------------------------------------------
/src/components/City/Header/CityMenuBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid } from '@material-ui/core';
5 |
6 | import SearchBar from 'components/SearchBar';
7 |
8 | function CityMenuBar({ handleSearch }) {
9 | return (
10 |
11 |
15 |
16 | );
17 | }
18 |
19 | CityMenuBar.propTypes = {
20 | handleSearch: PropTypes.func.isRequired,
21 | };
22 |
23 | export default CityMenuBar;
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 | A clear and concise description of what the issue is about.
3 |
4 | ## Screenshots
5 |
6 |
7 | ## Files
8 | A list of relevant files for this issue. This will help people navigate the project and offer some clues of where to start.
9 |
10 | ## To Reproduce
11 | If this issue is describing a bug, include some steps to reproduce the behavior.
12 |
13 | ## Tasks
14 | Include specific tasks in the order they need to be done in. Include links to specific lines of code where the task should happen at.
15 | - [ ] Task 1
16 | - [ ] Task 2
17 | - [ ] Task 3
18 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import PropTypes from 'prop-types';
4 | import { withRouter } from 'next/link';
5 |
6 | function ScrollToTop({ children, location }) {
7 | useEffect((prevProps) => {
8 | if (location !== prevProps.location) {
9 | window.scrollTo(0, 0);
10 | }
11 | });
12 |
13 | return children;
14 | }
15 |
16 | ScrollToTop.propTypes = {
17 | location: PropTypes.shape({}).isRequired,
18 | children: PropTypes.shape({}),
19 | };
20 |
21 | ScrollToTop.defaultProps = {
22 | children: null,
23 | };
24 |
25 | export default withRouter(ScrollToTop);
26 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "babel-eslint",
4 | "extends": ["airbnb", "plugin:prettier/recommended", "prettier/react"],
5 | "env": {
6 | "browser": true,
7 | "jest": true
8 | },
9 | "plugins": ["module-resolver"],
10 | "settings": {
11 | "import/resolver": {
12 | "babel-module": {}
13 | }
14 | },
15 | "rules": {
16 | "module-resolver/use-alias": 2,
17 | "react/prop-types": 0,
18 | "react/jsx-filename-extension": [
19 | 1,
20 | {
21 | "extensions": [".js", ".jsx"]
22 | }
23 | ],
24 | "react/jsx-props-no-spreading": 0,
25 | "no-nested-ternary": "off"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/Link/NextComposed.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/anchor-has-content */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import NextLink from 'next/link';
5 |
6 | const NextComposed = React.forwardRef(function NextComposed(props, ref) {
7 | const { as, href, prefetch, ...other } = props;
8 |
9 | return (
10 |
11 |
12 |
13 | );
14 | });
15 |
16 | NextComposed.propTypes = {
17 | as: PropTypes.string,
18 | href: PropTypes.string,
19 | prefetch: PropTypes.bool,
20 | };
21 |
22 | NextComposed.defaultProps = {
23 | as: undefined,
24 | href: undefined,
25 | prefetch: undefined,
26 | };
27 | export default NextComposed;
28 |
--------------------------------------------------------------------------------
/src/components/Embeds/AirMap.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { CITIES_LOCATION } from 'api';
5 |
6 | function AirMap({ city }) {
7 | return (
8 |
22 | );
23 | }
24 |
25 | AirMap.propTypes = {
26 | city: PropTypes.string.isRequired,
27 | };
28 |
29 | export default AirMap;
30 |
--------------------------------------------------------------------------------
/src/components/DocumentHead/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Helmet } from 'react-helmet';
5 |
6 | import PageHeads, { URLS } from 'components/DocumentHead/PageHeads';
7 | import Favicon from 'components/Favicon';
8 |
9 | function DocumentHead({ url }) {
10 | const matchUrl = (obj) => obj.url === url;
11 | const matchDefault = (obj) => obj.url === '/';
12 | const head =
13 | (url && PageHeads.find(matchUrl)) || PageHeads.find(matchDefault);
14 |
15 | return (
16 | <>
17 |
18 | {head.title}
19 |
20 |
21 | >
22 | );
23 | }
24 |
25 | DocumentHead.propTypes = {
26 | url: PropTypes.string,
27 | };
28 |
29 | DocumentHead.defaultProps = {
30 | url: null,
31 | };
32 |
33 | export { URLS };
34 | export default DocumentHead;
35 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
4 |
5 | Fixes # (issue)
6 |
7 | ## Type of change
8 |
9 | Please delete options that are not relevant.
10 |
11 | - [ ] Bug fix (non-breaking change which fixes an issue)
12 | - [ ] New feature (non-breaking change which adds functionality)
13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
14 | - [ ] This change requires a documentation update
15 |
16 |
17 | ## Screenshots
18 |
19 |
20 | ## Checklist:
21 |
22 | - [ ] My code follows the style guidelines of this project
23 | - [ ] I have performed a self-review of my own code
24 | - [ ] I have commented my code, particularly in hard-to-understand areas
25 | - [ ] I have made corresponding changes to the documentation
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AirCityHeaderContent/CityGauge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { withWidth } from '@material-ui/core';
5 |
6 | import DigitalGauge from 'components/Header/JumboContent/AirCityHeaderContent/DigitalGauge';
7 | import RadialGauge from 'components/Header/JumboContent/AirCityHeaderContent/RadialGauge';
8 |
9 | function CityGauge({ airPollMeasurement, airPollDescription, width }) {
10 | const Gauge = width === 'xs' || width === 'sm' ? DigitalGauge : RadialGauge;
11 |
12 | return (
13 |
17 | );
18 | }
19 |
20 | CityGauge.propTypes = {
21 | airPollMeasurement: PropTypes.string.isRequired,
22 | airPollDescription: PropTypes.string.isRequired,
23 | width: PropTypes.string.isRequired,
24 | };
25 |
26 | export default withWidth()(CityGauge);
27 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/HealthAndClimateHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import HealthClimateContent from 'components/Header/JumboContent/HealthClimateContent';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | jumbotron: {
10 | flexGrow: 1,
11 | backgroundColor: theme.palette.primary.light,
12 | borderRadius: 'none',
13 | [theme.breakpoints.up('md')]: {
14 | height: 450,
15 | },
16 | },
17 | }));
18 |
19 | function HealthClimateHeader() {
20 | const classes = useStyles();
21 | return (
22 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default HealthClimateHeader;
36 |
--------------------------------------------------------------------------------
/src/components/Navigation.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import Router from 'next/router';
4 |
5 | import Navbar from 'components/Header/Navbar';
6 |
7 | const DEFAULT_LOCATION = 'africa';
8 |
9 | const DASHBOARD_PATHNAME = '/dashboard';
10 |
11 | function Navigation({ location: locationProp }) {
12 | const [location, setLocation] = useState(locationProp);
13 |
14 | const handleSearch = (option) => {
15 | const searchedLocation = (option && option.value) || DEFAULT_LOCATION;
16 | if (!location?.length || searchedLocation !== location) {
17 | setLocation(searchedLocation);
18 | const locationUrl = `${DASHBOARD_PATHNAME}/[[...id]]`;
19 | const locationAs = `${DASHBOARD_PATHNAME}/${searchedLocation}`;
20 | Router.push(locationUrl, locationAs);
21 | }
22 | };
23 |
24 | return ;
25 | }
26 |
27 | Navigation.propTypes = {};
28 | Navigation.defaultProps = {};
29 |
30 | export default Navigation;
31 |
--------------------------------------------------------------------------------
/src/components/Embeds/AirGauge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import CityGauge from 'components/Header/JumboContent/AirCityHeaderContent/CityGauge';
8 |
9 | const useStyles = makeStyles({
10 | root: {
11 | backgroundColor: 'rgb(95, 191, 130)',
12 | height: '100vh',
13 | maxWidth: '100%',
14 | width: '100vw',
15 | },
16 | });
17 |
18 | function AirGauge({ data, ...props }) {
19 | const classes = useStyles(props);
20 |
21 | return (
22 |
23 |
27 |
28 | );
29 | }
30 |
31 | AirGauge.propTypes = {
32 | data: PropTypes.shape({
33 | average: PropTypes.string,
34 | averageDescription: PropTypes.string,
35 | }).isRequired,
36 | };
37 |
38 | export default AirGauge;
39 |
--------------------------------------------------------------------------------
/src/components/Air/AirHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Grid from '@material-ui/core/Grid';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import AirHeaderContent from 'components/Header/JumboContent/AirHeaderContent';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | jumbotron: {
11 | flexGrow: 1,
12 | backgroundColor: theme.palette.primary.light,
13 | borderRadius: 'none',
14 | [theme.breakpoints.up('md')]: {
15 | height: 450,
16 | },
17 | },
18 | }));
19 |
20 | function AirHeader({ handleSearch }) {
21 | const classes = useStyles();
22 | return (
23 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | AirHeader.propTypes = {
37 | handleSearch: PropTypes.func.isRequired,
38 | };
39 |
40 | export default AirHeader;
41 |
--------------------------------------------------------------------------------
/src/components/JoinNetwork/JoinHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import JoinNetworkContent from 'components/Header/JumboContent/JoinHeaderContent';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | jumbotron: {
10 | flexGrow: 1,
11 | backgroundColor: theme.palette.primary.light,
12 | borderRadius: 'none',
13 | [theme.breakpoints.up('md')]: {
14 | height: 450,
15 | },
16 | },
17 | }));
18 |
19 | function JoinHeader() {
20 | const classes = useStyles();
21 | return (
22 |
28 |
29 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default JoinHeader;
39 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/HealthClimateContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles({
8 | titleSection: {
9 | flexGrow: 1,
10 | textAlign: 'center',
11 | paddingTop: '2rem',
12 | },
13 | headerText: {
14 | color: 'white',
15 | paddingBottom: '1rem',
16 | },
17 | });
18 |
19 | function HealthClimateContent({ title }) {
20 | const classes = useStyles();
21 | return (
22 |
28 |
29 |
30 | {title}
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | HealthClimateContent.propTypes = {
38 | title: PropTypes.string,
39 | };
40 | HealthClimateContent.defaultProps = {
41 | title: '',
42 | };
43 |
44 | export default HealthClimateContent;
45 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import Link from 'components/Link';
7 | import Logo from 'components/Logo';
8 | import bglanding from 'assets/images/background/bglanding.jpg';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | root: {
12 | flexGrow: 1,
13 | textAlign: 'center',
14 | backgroundImage: `url(${bglanding})`,
15 | backgroundRepeat: 'no-repeat',
16 | backgroundSize: 'cover',
17 | height: '100vh',
18 | [theme.breakpoints.up('md')]: {
19 | height: '100vh',
20 | },
21 | },
22 | img: {
23 | height: '8rem',
24 | maxWidth: '100%',
25 | },
26 | }));
27 |
28 | function Loading() {
29 | const classes = useStyles();
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | export default Loading;
42 |
--------------------------------------------------------------------------------
/src/components/DataArchives/DataArchivesHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles(() => ({
8 | jumbotron: {
9 | flexGrow: 1,
10 | borderRadius: 'none',
11 | height: '20rem',
12 | },
13 | headline: {
14 | textAlign: 'center',
15 | },
16 | caption: {
17 | display: 'block',
18 | textTransform: 'none',
19 | },
20 | }));
21 |
22 | function DataArchivesHeader() {
23 | const classes = useStyles();
24 | return (
25 |
31 |
32 |
33 | Sensors in Africa
34 |
35 | How to access data from http://api.sensors.africa
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default DataArchivesHeader;
44 |
--------------------------------------------------------------------------------
/src/components/About/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import MenuBar from 'components/Header/MenuBar';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | jumbotron: {
10 | flexGrow: 1,
11 | backgroundColor: theme.palette.secondary.main,
12 | borderRadius: 'none',
13 | [theme.breakpoints.up('md')]: {
14 | height: 450,
15 | },
16 | },
17 | link: {
18 | paddingRight: '0.2rem',
19 | paddingLeft: '0.2rem',
20 | color: 'white',
21 | '&:hover': {
22 | color: '#2FB56B',
23 | },
24 | },
25 | }));
26 |
27 | function AboutHeader(props) {
28 | const classes = useStyles(props);
29 | return (
30 |
36 |
37 |
38 |
39 |
40 | Sensors Dashboard
41 |
42 |
43 | );
44 | }
45 |
46 | export default AboutHeader;
47 |
--------------------------------------------------------------------------------
/src/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import Providers from 'next-auth/providers';
3 |
4 | const options = {
5 | // Configure one or more authentication providers
6 | providers: [
7 | Providers.Google({
8 | clientId: process.env.GOOGLE_ID,
9 | clientSecret: process.env.GOOGLE_SECRET,
10 | }),
11 | ],
12 |
13 | callbacks: {
14 | /**
15 | * @param {object} user User object
16 | * @param {object} account Provider account
17 | * @param {object} profile Provider profile
18 | * @return {boolean} Return `true` (or a modified JWT) to allow sign in
19 | * Return `false` to deny access
20 | */
21 | signIn: async (user) => {
22 | // check useremail against db/env to see if they are allowed to login
23 | if (process.env.ALLOWED_EMAILS.includes(user.email)) {
24 | return Promise.resolve(true);
25 | }
26 | return Promise.resolve(false);
27 | },
28 | },
29 |
30 | pages: {
31 | signIn: '/',
32 | error: '/404', // Error code passed in query string as ?error=
33 | },
34 | };
35 |
36 | export default (req, res) => NextAuth(req, res, options);
37 |
--------------------------------------------------------------------------------
/src/components/SensorMap/IframeComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles({
7 | fullHeight: {
8 | display: 'inline-block',
9 | margin: '0 auto',
10 | backgroundColor: 'white',
11 | },
12 | });
13 |
14 | function Iframe({
15 | title,
16 | src,
17 | height,
18 | width,
19 | frameBorder,
20 | scrolling,
21 | ...props
22 | }) {
23 | const classes = useStyles(props);
24 |
25 | return (
26 |
36 | );
37 | }
38 |
39 | Iframe.propTypes = {
40 | title: PropTypes.string.isRequired,
41 | src: PropTypes.string.isRequired,
42 | height: PropTypes.string,
43 | width: PropTypes.string,
44 | frameBorder: PropTypes.string,
45 | scrolling: PropTypes.string,
46 | };
47 |
48 | Iframe.defaultProps = {
49 | height: '',
50 | width: '',
51 | frameBorder: '0',
52 | scrolling: 'auto',
53 | };
54 | export default Iframe;
55 |
--------------------------------------------------------------------------------
/src/components/Showcase/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 |
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import StoryList from 'components/Showcase/StoryList';
8 |
9 | const useStyles = makeStyles({
10 | root: {
11 | paddingTop: '4rem',
12 | backgroundColor: 'white',
13 | paddingBottom: '4rem',
14 | },
15 | headline: {
16 | textAlign: 'center',
17 | marginBottom: '2rem',
18 | },
19 | headlineTitle: {
20 | textAlign: 'center',
21 | paddingBottom: '1rem',
22 | },
23 | });
24 |
25 | function Showcase() {
26 | const classes = useStyles();
27 | return (
28 |
29 |
30 |
31 | SHOWCASE
32 |
33 |
34 | Here are stories from all around the world on air quality and its
35 | effects
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export default Showcase;
46 |
--------------------------------------------------------------------------------
/src/components/AfricaMap/IframeComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { withStyles } from '@material-ui/core/styles';
5 |
6 | const styles = {
7 | fullHeight: {
8 | display: 'inline-block',
9 | margin: '0 auto',
10 | backgroundColor: 'white',
11 | },
12 | };
13 |
14 | function Iframe({
15 | classes,
16 | title,
17 | src,
18 | height,
19 | width,
20 | frameBorder,
21 | scrolling,
22 | }) {
23 | return (
24 |
34 | );
35 | }
36 |
37 | Iframe.propTypes = {
38 | classes: PropTypes.shape({}).isRequired,
39 | title: PropTypes.string.isRequired,
40 | src: PropTypes.string.isRequired,
41 | height: PropTypes.string,
42 | width: PropTypes.string,
43 | frameBorder: PropTypes.string,
44 | scrolling: PropTypes.string,
45 | };
46 |
47 | Iframe.defaultProps = {
48 | height: '',
49 | width: '',
50 | frameBorder: '0',
51 | scrolling: 'auto',
52 | };
53 | export default withStyles(styles)(Iframe);
54 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withImages = require('next-images');
2 |
3 | module.exports = withImages({
4 | webpack(config, { isServer }) {
5 | // Important: return the modified config
6 |
7 | // https://github.com/jsoma/tabletop/issues/158
8 | if (!isServer) {
9 | // eslint-disable-next-line no-param-reassign
10 | config.externals = ['tls', 'net', 'fs'];
11 | }
12 | return config;
13 | },
14 | async headers() {
15 | return [
16 | {
17 | // matching all API routes
18 | source: '/api/:path*',
19 | headers: [
20 | { key: 'Access-Control-Allow-Credentials', value: 'true' },
21 | {
22 | key: 'Access-Control-Allow-Origin',
23 | value: 'https://map.data4sdgs.sensors.africa',
24 | },
25 | {
26 | key: 'Access-Control-Allow-Methods',
27 | value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT',
28 | },
29 | {
30 | key: 'Access-Control-Allow-Headers',
31 | value:
32 | 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
33 | },
34 | ],
35 | },
36 | ];
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/src/components/Air/Gauge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import GaugeChart from './GaugeChart';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | margin: '0 auto',
12 | width: '50vw',
13 | [theme.breakpoints.up('sm')]: {
14 | width: '9rem',
15 | },
16 | [theme.breakpoints.up('md')]: {
17 | width: '12rem',
18 | padding: '0 1rem',
19 | },
20 | [theme.breakpoints.up('lg')]: {
21 | width: '15rem',
22 | },
23 | },
24 | caption: {
25 | paddingTop: '1rem',
26 | textAlign: 'center',
27 | },
28 | }));
29 |
30 | function Gauge({ percentage, caption }) {
31 | const classes = useStyles();
32 | return (
33 |
34 |
35 |
36 | {caption}
37 |
38 |
39 | );
40 | }
41 |
42 | Gauge.propTypes = {
43 | percentage: PropTypes.number.isRequired,
44 | caption: PropTypes.string.isRequired,
45 | };
46 |
47 | export default Gauge;
48 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | function airData() {
2 | return [
3 | {
4 | date: 'Mon, Nov 23',
5 | averagePM: Math.random() * 24,
6 | },
7 | {
8 | date: 'Tue, Nov 24',
9 | averagePM: Math.random() * 24,
10 | },
11 | {
12 | date: 'Wed, Nov 25',
13 | averagePM: Math.random() * 24,
14 | },
15 | {
16 | date: 'Thu, Nov 26',
17 | averagePM: Math.random() * 24,
18 | },
19 | {
20 | date: 'Fri, Nov 27',
21 | averagePM: Math.random() * 24,
22 | },
23 | {
24 | date: 'Sat, Nov 28',
25 | averagePM: Math.random() * 24,
26 | },
27 | {
28 | date: 'Sun, Nov 29',
29 | averagePM: Math.random() * 24,
30 | },
31 | {
32 | date: 'Mon, Nov 30',
33 | averagePM: Math.random() * 24,
34 | },
35 | ];
36 | }
37 |
38 | const config = {
39 | airData: { name: 'Kenya', data: airData() },
40 | multiAirData: [
41 | { name: 'Kenya', data: airData() },
42 | { name: 'South Africa', data: airData() },
43 | { name: 'Nigeria', data: airData() },
44 | ],
45 | leastAirData: [
46 | { name: 'Ghana', data: airData() },
47 | { name: 'Tanzania', data: airData() },
48 | { name: 'Togo', data: airData() },
49 | ],
50 | };
51 |
52 | export default config;
53 |
--------------------------------------------------------------------------------
/src/components/SensorsInfo/HardwareHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import HardwareHeaderContent from 'components/Header/JumboContent/HardwareHeaderContent';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | jumbotron: {
10 | paddingBottom: '2rem',
11 | backgroundColor: theme.palette.primary.light,
12 | borderRadius: 'none',
13 | [theme.breakpoints.up('md')]: {
14 | paddingTop: '2rem',
15 | height: 450,
16 | },
17 | },
18 | }));
19 |
20 | function HardwareHeader() {
21 | const classes = useStyles();
22 | return (
23 |
29 |
30 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default HardwareHeader;
40 |
--------------------------------------------------------------------------------
/src/components/Filter/Select.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import InputLabel from '@material-ui/core/InputLabel';
4 | import MenuItem from '@material-ui/core/MenuItem';
5 | import FormControl from '@material-ui/core/FormControl';
6 | import Select from '@material-ui/core/Select';
7 |
8 | const useStyles = makeStyles({
9 | root: {
10 | maxWidth: 240,
11 | },
12 | });
13 |
14 | export default function BasicSelect({ onChange }) {
15 | const classes = useStyles();
16 | const [value, setValue] = React.useState('');
17 |
18 | const handleChange = (event) => {
19 | setValue(event.target.value);
20 | onChange(event.target.value);
21 | };
22 |
23 | return (
24 |
25 |
26 | Particulate Matter
27 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Air/HealthEffects.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import Gauge from './Gauge';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | svgContainer: {
10 | paddingTop: '3rem',
11 | paddingBottom: '2rem',
12 | width: '100vw',
13 | margin: '0 auto',
14 | [theme.breakpoints.up('md')]: {
15 | width: '59.625rem',
16 | },
17 | [theme.breakpoints.up('lg')]: {
18 | width: '79.5rem',
19 | },
20 | },
21 | }));
22 |
23 | function HealthEffects() {
24 | const classes = useStyles();
25 | return (
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
49 | export default HealthEffects;
50 |
--------------------------------------------------------------------------------
/src/components/IconLogo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import classNames from 'classnames';
5 |
6 | import Link from 'components/Link';
7 |
8 | import logo from 'assets/images/logos/logoExperimental.png';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | defaultBadge: {
12 | zIndex: 1301,
13 | top: '5.8rem',
14 | right: '1.65rem',
15 | color: theme.palette.primary.dark,
16 | fontSize: theme.typography.caption.fontSize,
17 | },
18 | defaultActiveBadge: {
19 | zIndex: 1301,
20 | top: '5.8rem',
21 | right: '1.65rem',
22 | fontSize: theme.typography.caption.fontSize,
23 | },
24 | landingBadge: {
25 | zIndex: 1301,
26 | top: '7.175rem',
27 | right: '1.65rem',
28 | fontSize: theme.typography.caption.fontSize,
29 | },
30 | logo: {
31 | zIndex: 1301,
32 | position: 'relative',
33 | },
34 | img: {
35 | height: 50,
36 | [theme.breakpoints.down('sm')]: {
37 | height: 40,
38 | },
39 | },
40 | }));
41 |
42 | function IconLogo() {
43 | const classes = useStyles();
44 | const imgClassName = classNames(classes.logo, classes.img);
45 |
46 | return (
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | export default IconLogo;
54 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/ShareButton/Embed.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { makeStyles } from '@material-ui/core/styles';
4 |
5 | const useStyles = makeStyles({
6 | container: {
7 | display: 'flex',
8 | flexWrap: 'wrap',
9 | paddingBottom: '3rem',
10 | },
11 | input: {
12 | width: '31.25rem',
13 | },
14 | dense: {
15 | marginTop: 16,
16 | },
17 | menu: {
18 | width: 200,
19 | },
20 | });
21 | function Embed({ city }) {
22 | const classes = useStyles();
23 | const iframe = `
24 |
33 | `;
34 | return (
35 |
40 | );
41 | }
42 |
43 | Embed.propTypes = {
44 | city: PropTypes.shape({
45 | slug: PropTypes.string.isRequired,
46 | name: PropTypes.string.isRequired,
47 | }).isRequired,
48 | };
49 |
50 | export default Embed;
51 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/JoinHeaderContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles({
8 | titleSection: {
9 | flexGrow: 1,
10 | textAlign: 'center',
11 | paddingTop: '2rem',
12 | },
13 | headerText: {
14 | color: 'white',
15 | paddingBottom: '1rem',
16 | },
17 | });
18 |
19 | function JoinNetworkContent({ title, subheading }) {
20 | const classes = useStyles();
21 | return (
22 |
28 |
29 |
30 | {title}
31 |
32 |
33 |
34 |
35 | {subheading}
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | JoinNetworkContent.propTypes = {
43 | title: PropTypes.string,
44 | subheading: PropTypes.string,
45 | };
46 | JoinNetworkContent.defaultProps = {
47 | title: '',
48 | subheading: '',
49 | };
50 |
51 | export default JoinNetworkContent;
52 |
--------------------------------------------------------------------------------
/src/pages/dashboard/docs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Router from 'next/router';
4 |
5 | import { useSession } from 'next-auth/client';
6 |
7 | import DataArchives from 'components/DataArchives/DataArchives';
8 | import Footer from 'components/Footer';
9 | import Navigation from 'components/Navigation';
10 | import Tokens from 'components/Tokens';
11 |
12 | function Data({ tokens }) {
13 | const [session, loading] = useSession();
14 | if (loading) return null;
15 |
16 | if (!loading && !session) {
17 | Router.push('/');
18 | }
19 |
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 | >
27 | );
28 | }
29 |
30 | export async function getStaticProps() {
31 | const airNowToken = process.env.AIR_NOW || null;
32 | const airQOToken = process.env.AIRQO || null;
33 | const data4SDGToken = process.env.DATA4_DSGS || null;
34 | const purpleAirToken = process.env.PURPLE_AIR || null;
35 | const smartCitizenToken = process.env.SMART_CITIZEN || null;
36 |
37 | // Pass data to the page via props
38 | return {
39 | props: {
40 | tokens: {
41 | airNowToken,
42 | airQOToken,
43 | data4SDGToken,
44 | purpleAirToken,
45 | smartCitizenToken,
46 | },
47 | },
48 | revalidate: 300, // seconds
49 | };
50 | }
51 |
52 | export default Data;
53 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AboutHeaderContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PropTypes from 'prop-types';
4 |
5 | import { Grid, Typography } from '@material-ui/core';
6 | import { makeStyles } from '@material-ui/core/styles';
7 |
8 | const useStyles = makeStyles({
9 | titleSection: {
10 | flexGrow: 1,
11 | textAlign: 'center',
12 | paddingTop: '2rem',
13 | },
14 | headerText: {
15 | color: 'white',
16 | paddingBottom: '1rem',
17 | textTransform: 'none',
18 | },
19 | });
20 |
21 | function AboutHeaderContent({ title, subheading }) {
22 | const classes = useStyles();
23 | return (
24 |
30 |
31 |
32 | {title}
33 |
34 |
35 |
36 |
37 | {subheading}
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | AboutHeaderContent.propTypes = {
45 | title: PropTypes.string,
46 | subheading: PropTypes.string,
47 | };
48 | AboutHeaderContent.defaultProps = {
49 | title: '',
50 | subheading: '',
51 | };
52 | export default AboutHeaderContent;
53 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/DataArchivesHeaderContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles({
8 | titleSection: {
9 | flexGrow: 1,
10 | textAlign: 'center',
11 | paddingTop: '2rem',
12 | },
13 | headerText: {
14 | color: 'white',
15 | paddingBottom: '1rem',
16 | textTransform: 'none',
17 | },
18 | });
19 |
20 | function DataArchivesHeaderContent({ title, subheading }) {
21 | const classes = useStyles();
22 | return (
23 |
29 |
30 |
31 | {title}
32 |
33 |
34 |
35 |
36 | {subheading}
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | DataArchivesHeaderContent.propTypes = {
44 | title: PropTypes.string,
45 | subheading: PropTypes.string,
46 | };
47 | DataArchivesHeaderContent.defaultProps = {
48 | title: '',
49 | subheading: '',
50 | };
51 |
52 | export default DataArchivesHeaderContent;
53 |
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from 'next/app';
4 | import Router from 'next/router';
5 |
6 | import { ThemeProvider } from '@material-ui/core/styles';
7 | import CssBaseline from '@material-ui/core/CssBaseline';
8 |
9 | import { library } from '@fortawesome/fontawesome-svg-core';
10 | import { fab } from '@fortawesome/free-brands-svg-icons';
11 | import { faSearch } from '@fortawesome/free-solid-svg-icons';
12 | import { Provider } from 'next-auth/client';
13 |
14 | import 'assets/css/index.css';
15 | import 'assets/css/App.css';
16 |
17 | import theme from 'theme';
18 |
19 | import * as gtag from 'lib/gtag';
20 |
21 | library.add(fab, faSearch);
22 |
23 | Router.events.on('routeChangeComplete', (url) => gtag.pageview(url));
24 |
25 | export default class MainApp extends App {
26 | componentDidMount() {
27 | // Remove the server-side injected CSS.
28 | const jssStyles = document.querySelector('#jss-server-side');
29 | if (jssStyles) {
30 | jssStyles.parentElement.removeChild(jssStyles);
31 | }
32 | }
33 |
34 | render() {
35 | const { Component, pageProps } = this.props;
36 | return (
37 |
38 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
39 |
40 |
41 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AirHeaderContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import SearchBar from 'components/SearchBar';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | titleSection: {
11 | flexGrow: 1,
12 | textAlign: 'center',
13 | paddingTop: '6rem',
14 | [theme.breakpoints.up('md')]: {
15 | paddingRight: '20%',
16 | paddingLeft: '20%',
17 | },
18 | [theme.breakpoints.up('lg')]: {
19 | paddingRight: '25%',
20 | paddingLeft: '25%',
21 | },
22 | },
23 | headerText: {
24 | color: 'white',
25 | paddingBottom: '1rem',
26 | },
27 | searchBar: {
28 | paddingBottom: '6rem',
29 | },
30 | }));
31 |
32 | function AirHeaderContent({ handleSearch }) {
33 | const classes = useStyles();
34 | return (
35 |
41 |
42 |
43 | SENSOR DATA COLLATED
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | AirHeaderContent.propTypes = {
54 | handleSearch: PropTypes.func.isRequired,
55 | };
56 |
57 | export default AirHeaderContent;
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data4SDGS Air Quality Dashboard
2 |
3 | ## Contributing
4 |
5 | If you'd like to contribute to sensors.AFRICA, check out the [CONTRIBUTING.md](./CONTRIBUTING.md) file on how to get started; or jump right into our [GitHub issues](issues).
6 |
7 | ## Attribution
8 |
9 | > If we have seen further it is by standing on the shoulders of giants. - Isaac Newton
10 |
11 | ### ✨ Contributors ✨
12 |
13 | [TODO](https://www.npmjs.com/package/all-contributors-cli)
14 |
15 | ---
16 |
17 | ## License
18 |
19 | GNU General Public License v3.0
20 |
21 | sensors.AFRICA is a citizen-science focused project by Code for Africa that seeks to address data gaps by providing low cost sensors, which people can use to measure and monitor the quality of the air, water, and environment in their communities. This web app seeks to be the public portal through which most users would discover and explore the data and intiative.
22 |
23 | Copyright (C) 2018 Code for Africa
24 |
25 | This program is free software: you can redistribute it and/or modify
26 | it under the terms of the GNU General Public License as published by
27 | the Free Software Foundation, either version 3 of the License, or
28 | (at your option) any later version.
29 |
30 | This program is distributed in the hope that it will be useful,
31 | but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | GNU General Public License for more details.
34 |
35 | You should have received a copy of the GNU General Public License
36 | along with this program. If not, see .
37 |
--------------------------------------------------------------------------------
/src/components/AfricaMap/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { withStyles } from '@material-ui/core/styles';
6 |
7 | import IframeComponent from './IframeComponent';
8 |
9 | const styles = (theme) => ({
10 | root: {
11 | flexGrow: 1,
12 | height: 610,
13 | width: '100%',
14 | marginBottom: '3rem',
15 | backgroundColor: 'white',
16 | },
17 | headline: {
18 | textAlign: 'center',
19 | paddingBottom: theme.spacing(3),
20 | },
21 | caption: {
22 | textTransform: 'none',
23 | },
24 | });
25 |
26 | function AfricaMap({ classes }) {
27 | return (
28 |
34 |
35 |
36 | SENSORS IN AFRICA
37 |
38 | * Click a sensor to view latest readings.
39 |
40 |
41 |
42 |
43 |
51 |
52 |
53 | );
54 | }
55 |
56 | AfricaMap.propTypes = {
57 | classes: PropTypes.shape({}).isRequired,
58 | };
59 | export default withStyles(styles)(AfricaMap);
60 |
--------------------------------------------------------------------------------
/src/components/About/Air/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import AboutHeaderContent from 'components/Header/JumboContent/AboutHeaderContent';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | jumbotron: {
10 | flexGrow: 1,
11 | backgroundColor: theme.palette.primary.light,
12 | borderRadius: 'none',
13 | [theme.breakpoints.up('md')]: {
14 | height: 450,
15 | },
16 | },
17 | link: {
18 | paddingRight: '0.2rem',
19 | paddingLeft: '0.2rem',
20 | color: 'white',
21 | '&:hover': {
22 | color: '#164B3E',
23 | },
24 | },
25 | }));
26 |
27 | function AboutHeader(props) {
28 | const classes = useStyles(props);
29 | return (
30 |
36 |
37 |
43 | Luftdaten project
44 | ,
45 | '. The initiative was seed-funded by innovateAFRICA and is being incubated by Code for Africa. ',
46 | ]}
47 | />
48 |
49 |
50 | );
51 | }
52 |
53 | export default AboutHeader;
54 |
--------------------------------------------------------------------------------
/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PropTypes from 'prop-types';
4 |
5 | import Badge from '@material-ui/core/Badge';
6 | import { makeStyles } from '@material-ui/core/styles';
7 |
8 | import classNames from 'classnames';
9 |
10 | import logowhite from 'assets/images/logos/logowhite.png';
11 |
12 | const useStyles = makeStyles((theme) => ({
13 | defaultBadge: {
14 | zIndex: 1301,
15 | top: '5.8rem',
16 | right: '1.65rem',
17 | color: theme.palette.primary.dark,
18 | fontSize: theme.typography.caption.fontSize,
19 | },
20 | defaultActiveBadge: {
21 | zIndex: 1301,
22 | top: '5.8rem',
23 | right: '1.65rem',
24 | fontSize: theme.typography.caption.fontSize,
25 | },
26 | landingBadge: {
27 | zIndex: 1301,
28 | top: '7.175rem',
29 | right: '1.65rem',
30 | fontSize: theme.typography.caption.fontSize,
31 | },
32 | logo: {
33 | zIndex: 1301,
34 | position: 'relative',
35 | },
36 | img: {},
37 | }));
38 |
39 | function Logo({ badge, active }) {
40 | const classes = useStyles();
41 | const activeStatus = active && badge === 'default' ? 'Active' : '';
42 | const imgClassName = classNames(classes.logo, classes.img);
43 | return (
44 |
48 |
54 |
55 | );
56 | }
57 |
58 | Logo.propTypes = {
59 | active: PropTypes.bool,
60 | badge: PropTypes.string,
61 | };
62 |
63 | Logo.defaultProps = {
64 | active: false,
65 | badge: 'default',
66 | };
67 |
68 | export default Logo;
69 |
--------------------------------------------------------------------------------
/src/components/City/Header/CityHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, useMediaQuery } from '@material-ui/core';
5 | import { makeStyles, useTheme } from '@material-ui/core/styles';
6 |
7 | import AirCityHeaderContent from 'components/Header/JumboContent/AirCityHeaderContent';
8 | import CityMenuBar from 'components/City/Header/CityMenuBar';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | jumbotron: {
12 | flexGrow: 1,
13 | borderRadius: 'none',
14 | height: 450,
15 | [theme.breakpoints.up('md')]: {
16 | height: 646,
17 | },
18 | },
19 | }));
20 |
21 | function CityHeader({
22 | airPol,
23 | airPolDescription,
24 | aqColor,
25 | city,
26 | handleSearch,
27 | width,
28 | ...props
29 | }) {
30 | const classes = useStyles(props);
31 | const theme = useTheme();
32 | let backgroundColor = aqColor;
33 | if (useMediaQuery(theme.breakpoints.up('md'))) {
34 | backgroundColor = '#2fb56b';
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 |
46 |
47 |
48 | );
49 | }
50 |
51 | CityHeader.propTypes = {
52 | airPol: PropTypes.string.isRequired,
53 | airPolDescription: PropTypes.string.isRequired,
54 | aqColor: PropTypes.string.isRequired,
55 | city: PropTypes.shape({}).isRequired,
56 | handleSearch: PropTypes.func.isRequired,
57 | width: PropTypes.string.isRequired,
58 | };
59 |
60 | export default CityHeader;
61 |
--------------------------------------------------------------------------------
/src/components/Landing/Hero.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import Link from 'components/Link';
7 | import Logo from 'components/Logo';
8 | import bglanding from 'assets/images/background/bglanding.jpg';
9 | import Login from 'components/Login';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | root: {
13 | flexGrow: 1,
14 | textAlign: 'center',
15 | backgroundImage: `url(${bglanding})`,
16 | backgroundRepeat: 'no-repeat',
17 | backgroundSize: 'cover',
18 | height: '100vh',
19 | [theme.breakpoints.up('md')]: {
20 | height: '100vh',
21 | },
22 | },
23 | img: {
24 | height: '8rem',
25 | maxWidth: '100%',
26 | },
27 | subtitle: {
28 | marginTop: '1rem',
29 | marginBottom: '1.5rem',
30 | color: 'white',
31 | textAlign: 'center',
32 | [theme.breakpoints.down('sm')]: {
33 | fontSize: '17px', // add this to themes responsive?
34 | },
35 | },
36 | form: {
37 | textAlign: 'center',
38 | '& .MuiFormLabel-root.Mui-focused': {
39 | color: '#FFFFFF',
40 | },
41 | },
42 | }));
43 |
44 | function Hero(props) {
45 | const classes = useStyles();
46 | return (
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default Hero;
64 |
--------------------------------------------------------------------------------
/src/components/About/AboutContent/Content.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import PropTypes from 'prop-types';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | flex: 1,
11 | padding: '2rem',
12 | },
13 | mainTitle: {
14 | color: theme.palette.secondary.main,
15 | textAlign: 'center',
16 | fontSize: theme.typography.h6.fontSize,
17 | paddingBottom: theme.typography.h6.fontSize,
18 | },
19 | subTitle: {
20 | textAlign: 'center',
21 | fontWeight: 600,
22 | },
23 | body: {
24 | paddingTop: '1.5rem',
25 | },
26 | bodyCopy: {
27 | textAlign: 'left',
28 | },
29 | }));
30 |
31 | function Content({ title, description, backgroundColor }) {
32 | const classes = useStyles();
33 | return (
34 |
41 |
42 |
43 | {title}
44 |
45 |
46 |
47 |
48 | {description}
49 |
50 |
51 |
52 | );
53 | }
54 |
55 | Content.propTypes = {
56 | title: PropTypes.string.isRequired,
57 | description: PropTypes.string.isRequired,
58 | backgroundColor: PropTypes.string,
59 | };
60 |
61 | Content.defaultProps = {
62 | backgroundColor: '#F3F3F3',
63 | };
64 |
65 | export default Content;
66 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/HardwareHeaderContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles({
8 | titleSection: {
9 | flexGrow: 1,
10 | textAlign: 'center',
11 | paddingTop: '2rem',
12 | },
13 | headerText: {
14 | color: 'white',
15 | paddingTop: '1rem',
16 | },
17 | });
18 |
19 | function HardwareInfoHeaderContent({ title, subheading, secondsubheading }) {
20 | const classes = useStyles();
21 | return (
22 |
28 |
29 |
30 | {title}
31 |
32 |
33 |
34 |
35 |
36 | {subheading}
37 |
38 |
39 |
40 |
41 |
42 | {secondsubheading}
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | HardwareInfoHeaderContent.propTypes = {
51 | title: PropTypes.string,
52 | subheading: PropTypes.string,
53 | secondsubheading: PropTypes.string,
54 | };
55 |
56 | HardwareInfoHeaderContent.defaultProps = {
57 | title: '',
58 | subheading: '',
59 | secondsubheading: '',
60 | };
61 | export default HardwareInfoHeaderContent;
62 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/PollutionBurden.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid, Typography } from '@material-ui/core';
3 | import PropTypes from 'prop-types';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | flex: 1,
9 | padding: '2rem',
10 | textAlign: 'center',
11 | },
12 | img: {
13 | height: '5rem',
14 | maxWidth: '100%',
15 | [theme.breakpoints.up('md')]: {
16 | height: '100%',
17 | },
18 | },
19 | mainTitle: {
20 | color: '#fff',
21 | textAlign: 'center',
22 | fontSize: theme.typography.h6.fontSize,
23 | },
24 | body: {
25 | paddingTop: '1.5rem',
26 | },
27 | bodyCopy: {
28 | color: '#fff',
29 | textAlign: 'center',
30 | },
31 | }));
32 |
33 | function PollutionBurden({ icon, title, burden }) {
34 | const classes = useStyles();
35 | return (
36 |
42 |
43 |
44 |
45 |
46 |
47 | {title}
48 |
49 |
50 |
51 |
58 |
59 | {burden}
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | PollutionBurden.propTypes = {
67 | icon: PropTypes.string.isRequired,
68 | title: PropTypes.string.isRequired,
69 | burden: PropTypes.string.isRequired,
70 | };
71 |
72 | export default PollutionBurden;
73 |
--------------------------------------------------------------------------------
/src/components/Link/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classNames from 'classnames';
5 |
6 | import { useRouter } from 'next/router';
7 |
8 | import Button from '@material-ui/core/Button';
9 |
10 | import NextComposed from './NextComposed';
11 |
12 | // A styled version of the Next.js Link component:
13 | // https://nextjs.org/docs/#with-link
14 | function ButtonLink(props) {
15 | const {
16 | activeClassName = 'active',
17 | className: classNameProps,
18 | href,
19 | innerRef,
20 | naked,
21 | ...other
22 | } = props;
23 | const router = useRouter();
24 |
25 | const className = classNames(classNameProps, {
26 | [activeClassName]: router.pathname === href && activeClassName,
27 | });
28 |
29 | if (naked) {
30 | return (
31 |
37 | );
38 | }
39 |
40 | return (
41 |
48 | );
49 | }
50 |
51 | ButtonLink.propTypes = {
52 | activeClassName: PropTypes.string,
53 | as: PropTypes.string,
54 | className: PropTypes.string,
55 | href: PropTypes.string,
56 | innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
57 | naked: PropTypes.bool,
58 | onClick: PropTypes.func,
59 | prefetch: PropTypes.bool,
60 | };
61 |
62 | ButtonLink.defaultProps = {
63 | activeClassName: undefined,
64 | as: undefined,
65 | className: undefined,
66 | href: undefined,
67 | innerRef: undefined,
68 | naked: undefined,
69 | onClick: undefined,
70 | prefetch: undefined,
71 | };
72 |
73 | export default React.forwardRef((props, ref) => (
74 |
75 | ));
76 |
--------------------------------------------------------------------------------
/src/components/Link/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/anchor-has-content */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | import classNames from 'classnames';
6 |
7 | import { useRouter } from 'next/router';
8 |
9 | import MuiLink from '@material-ui/core/Link';
10 |
11 | import NextComposed from './NextComposed';
12 |
13 | // A styled version of the Next.js Link component:
14 | // https://nextjs.org/docs/#with-link
15 | function Link(props) {
16 | const {
17 | activeClassName = 'active',
18 | className: classNameProps,
19 | href,
20 | innerRef,
21 | naked,
22 | ...other
23 | } = props;
24 | const router = useRouter();
25 |
26 | const className = classNames(classNameProps, {
27 | [activeClassName]: router.pathname === href && activeClassName,
28 | });
29 |
30 | if (naked) {
31 | return (
32 |
38 | );
39 | }
40 |
41 | return (
42 |
49 | );
50 | }
51 |
52 | Link.propTypes = {
53 | activeClassName: PropTypes.string,
54 | as: PropTypes.string,
55 | className: PropTypes.string,
56 | href: PropTypes.string,
57 | innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({})]),
58 | naked: PropTypes.bool,
59 | onClick: PropTypes.func,
60 | prefetch: PropTypes.bool,
61 | };
62 |
63 | Link.defaultProps = {
64 | activeClassName: undefined,
65 | as: undefined,
66 | className: undefined,
67 | href: undefined,
68 | innerRef: undefined,
69 | naked: undefined,
70 | onClick: undefined,
71 | prefetch: undefined,
72 | };
73 |
74 | export default React.forwardRef((props, ref) => (
75 |
76 | ));
77 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Grid from '@material-ui/core/Grid';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import ArrowCards from 'components/City/HostSensors/ArrowCards';
7 | import HostCard from 'components/City/HostSensors/HostCard';
8 | import HostSensorButtons from 'components/City/HostSensors/HostSensorButtons';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | root: {
12 | flexGrow: 1,
13 | width: '100%',
14 | marginTop: theme.spacing(8),
15 | marginBottom: theme.spacing(4),
16 | },
17 | description: {
18 | marginTop: theme.spacing(4),
19 | },
20 | arrowDescription: {
21 | [theme.breakpoints.down('sm')]: {
22 | marginTop: theme.spacing(4),
23 | },
24 | },
25 | hostDescription: {
26 | [theme.breakpoints.down('sm')]: {
27 | marginTop: theme.spacing(8),
28 | },
29 | },
30 | mainGrid: {
31 | paddingRight: '4rem',
32 | paddingLeft: '4rem',
33 | },
34 | }));
35 |
36 | function HostSensor() {
37 | const classes = useStyles();
38 | return (
39 |
45 |
46 |
47 |
48 |
49 |
50 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | );
67 | }
68 |
69 | export default HostSensor;
70 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/HealthAndDiseaseBurden.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import HealthEffects from 'components/Air/HealthEffects';
7 | import Burden from 'components/HealthClimate/Burden';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | backgroundColor: 'white',
12 | display: 'block',
13 | },
14 | grid: {
15 | paddingTop: theme.spacing(2),
16 | paddingBottom: theme.spacing(2),
17 | },
18 | mainTitle: {
19 | textAlign: 'center',
20 | fontWeight: 800,
21 | fontSize: theme.typography.fontSize,
22 | },
23 | caption: {
24 | paddingTop: '1rem',
25 | textAlign: 'center',
26 | },
27 | graph: {
28 | margin: '0 auto',
29 | width: '9rem',
30 | [theme.breakpoints.up('md')]: {
31 | width: '12rem',
32 | padding: '0 1rem',
33 | },
34 | [theme.breakpoints.up('lg')]: {
35 | width: '15rem',
36 | },
37 | },
38 | titleGrid: {
39 | marginRight: '15%',
40 | marginLeft: '15%',
41 | },
42 | title: {
43 | textAlign: 'center',
44 | textTransform: 'None',
45 | },
46 | }));
47 |
48 | function HealthAndBurden() {
49 | const classes = useStyles();
50 | return (
51 |
57 |
58 |
63 | Health and Disease Burden
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | export default HealthAndBurden;
78 |
--------------------------------------------------------------------------------
/src/components/Header/MenuBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Grid from '@material-ui/core/Grid';
5 | import { withStyles } from '@material-ui/core/styles';
6 |
7 | import HamburgerMenu from 'components/Hambuger/HambugerMenu';
8 |
9 | const styles = (theme) => ({
10 | root: {
11 | flexGrow: 1,
12 | position: 'relative',
13 | [theme.breakpoints.up('md')]: {
14 | paddingRight: '8%',
15 | paddingLeft: '8%',
16 | },
17 | },
18 | children: {
19 | [theme.breakpoints.only('xs')]: {
20 | position: 'absolute',
21 | top: '8rem',
22 | left: '50%',
23 | transform: 'translate(-50%, -50%)',
24 | zIndex: 3,
25 | },
26 | },
27 | icon: {
28 | color: 'white',
29 | paddingTop: '3%',
30 | },
31 | iconContainer: {
32 | // paddingTop: '2rem'
33 | },
34 | });
35 |
36 | class MenuBar extends React.Component {
37 | constructor(props) {
38 | super(props);
39 |
40 | this.state = { menuOpen: false };
41 | this.handleToggle = this.handleToggle.bind(this);
42 | }
43 |
44 | handleToggle() {
45 | this.setState((prevState) => ({ menuOpen: !prevState.menuOpen }));
46 | }
47 |
48 | render() {
49 | const { classes, showMenu } = this.props;
50 | const { menuOpen } = this.state;
51 |
52 | return (
53 |
59 | {showMenu && (
60 |
61 |
62 |
66 |
67 |
68 | )}
69 |
70 | );
71 | }
72 | }
73 |
74 | MenuBar.propTypes = {
75 | showMenu: PropTypes.bool,
76 | };
77 |
78 | MenuBar.defaultProps = {
79 | showMenu: true,
80 | };
81 |
82 | export default withStyles(styles)(MenuBar);
83 |
--------------------------------------------------------------------------------
/src/components/City/SensorsQualityStats/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import DataTable from 'components/City/SensorsQualityStats/DataTable';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | flexGrow: 1,
12 | marginTop: theme.spacing(0),
13 | marginBottom: theme.spacing(4),
14 | },
15 | statsSummary: {
16 | width: '100%',
17 | [theme.breakpoints.up('md')]: {
18 | width: '19.875rem',
19 | },
20 | [theme.breakpoints.up('lg')]: {
21 | width: '26.5rem',
22 | },
23 | },
24 |
25 | // TODO(kilemensi): Currently statsSummary is not implemented yet so make
26 | // dataTable the only component
27 | qualityStats: {
28 | width: '100%',
29 | [theme.breakpoints.up('md')]: {
30 | width: '59.625rem',
31 | borderTop: '1px solid rgba(0,0,0,0.1)',
32 | borderBottom: '1px solid rgba(0,0,0,0.1)',
33 | },
34 | [theme.breakpoints.up('lg')]: {
35 | width: '79.5rem',
36 | },
37 | },
38 | }));
39 |
40 | function SensorsQualityStats({
41 | cityHumidityStats,
42 | cityTemperatureStats,
43 | cityP2Stats,
44 | }) {
45 | const classes = useStyles();
46 | return (
47 |
53 |
54 |
55 |
60 |
61 |
62 |
63 | );
64 | }
65 |
66 | SensorsQualityStats.propTypes = {
67 | cityHumidityStats: PropTypes.shape({}).isRequired,
68 | cityTemperatureStats: PropTypes.shape({}).isRequired,
69 | cityP2Stats: PropTypes.shape({}).isRequired,
70 | };
71 | export default SensorsQualityStats;
72 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AirCityHeaderContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import CityGauge from 'components/Header/JumboContent/AirCityHeaderContent/CityGauge';
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | flexGrow: 1,
12 | },
13 | gaugeContainer: {
14 | marginTop: '6rem',
15 | [theme.breakpoints.up('md')]: {
16 | marginTop: '1.875rem',
17 | },
18 | },
19 | city: {
20 | [theme.breakpoints.only('xs')]: {
21 | fontSize: theme.typography.h6.fontSize,
22 | },
23 | },
24 | }));
25 |
26 | function AirCityHeaderContent({ airPol, airPolDescription, city }) {
27 | const classes = useStyles();
28 | return (
29 |
35 |
44 |
45 | THE AIR POLLUTION IN{' '}
46 |
47 |
52 | {city.label}
53 |
54 |
55 |
56 |
60 |
61 |
62 | );
63 | }
64 |
65 | AirCityHeaderContent.propTypes = {
66 | city: PropTypes.shape({}).isRequired,
67 | classes: PropTypes.shape({}).isRequired,
68 | airPol: PropTypes.string.isRequired,
69 | airPolDescription: PropTypes.string.isRequired,
70 | };
71 |
72 | export default AirCityHeaderContent;
73 |
--------------------------------------------------------------------------------
/src/components/About/Stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button, Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import background from 'assets/images/background/bgstories.jpg';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | paddingTop: '2rem',
11 | height: 350,
12 | backgroundImage: `url('${background}')`,
13 | backgroundSize: 'cover',
14 | overflow: 'hidden',
15 | },
16 | titleSection: {
17 | flexGrow: 1,
18 | textAlign: 'center',
19 | color: 'white',
20 | },
21 | buttonContainer: {
22 | paddingTop: '2rem',
23 | },
24 | buttonLink: {
25 | textDecoration: 'none',
26 | },
27 | button: {
28 | color: theme.palette.primary.dark,
29 | fontWeight: 900,
30 | backgroundColor: '#fff',
31 | },
32 | }));
33 |
34 | function Stories(props) {
35 | const classes = useStyles(props);
36 |
37 | return (
38 |
42 |
48 |
49 |
53 | SENSORS STORIES
54 |
55 |
56 |
57 |
69 |
70 |
71 |
72 | );
73 | }
74 |
75 | export default Stories;
76 |
--------------------------------------------------------------------------------
/src/components/SensorMap/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | import IframeComponent from './IframeComponent';
8 |
9 | const MAP_URL = '//map.data4sdgs.sensors.africa';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | root: {
13 | flexGrow: 1,
14 | height: 560,
15 | width: '100%',
16 | backgroundColor: 'white',
17 | },
18 | headline: {
19 | textAlign: 'center',
20 | paddingBottom: theme.spacing(3),
21 | },
22 | caption: {
23 | display: 'block',
24 | textTransform: 'none',
25 | },
26 | fullHeight: {
27 | border: '1px solid #D6D6D6',
28 | },
29 | }));
30 |
31 | function Map({ zoom, latitude, longitude }) {
32 | const classes = useStyles();
33 | return (
34 |
40 |
41 |
50 |
51 |
52 | );
53 | }
54 |
55 | export function AfricaMap({ classes }) {
56 | return (
57 |
63 |
64 |
72 |
73 |
74 | );
75 | }
76 |
77 | Map.propTypes = {
78 | zoom: PropTypes.string.isRequired,
79 | latitude: PropTypes.string.isRequired,
80 | longitude: PropTypes.string.isRequired,
81 | };
82 |
83 | export default Map;
84 |
--------------------------------------------------------------------------------
/src/components/Average/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | margin: '0 10px',
9 | },
10 | section: {
11 | margin: '0 10px',
12 | width: '100%',
13 | [theme.breakpoints.up('md')]: {
14 | width: '59.625rem',
15 | },
16 | [theme.breakpoints.up('lg')]: {
17 | width: '76.125rem',
18 | },
19 | [theme.breakpoints.down('sm')]: {
20 | width: '100vw',
21 | },
22 | },
23 | container: {
24 | marginTop: '1rem',
25 | padding: '1.125rem',
26 | [theme.breakpoints.up('md')]: {
27 | padding: '1.125rem 2.625rem 1.25rem',
28 | },
29 | },
30 | textContainer: {
31 | margin: '40px 0',
32 | },
33 | title: {
34 | textTransform: 'uppercase',
35 | },
36 | }));
37 |
38 | function Average({ ...props }) {
39 | const classes = useStyles(props);
40 |
41 | return (
42 |
43 |
44 |
51 |
52 |
53 | 10h38
54 |
55 |
56 | Most dangerous time of the day in 56 African countries.
57 |
58 |
59 |
60 |
61 | Friday
62 |
63 |
64 | Most dangerous day of the week in 56 African countries.
65 |
66 |
67 |
68 |
69 |
70 | );
71 | }
72 |
73 | export default Average;
74 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/HostSensorButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Button } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | buttonContained: {
9 | width: '100%',
10 | backgroundColor: theme.palette.primary.light,
11 | color: '#fff',
12 | borderRadius: 0,
13 | fontWeight: 700,
14 | border: '1px solid transparent',
15 | '&:hover': {
16 | backgroundColor: theme.palette.primary.dark,
17 | border: '1px solid rgba(0, 0, 0, 0.23)',
18 | },
19 | [theme.breakpoints.up('md')]: {
20 | width: 'auto',
21 | margin: theme.spacing(2),
22 | },
23 | },
24 | buttonOutlined: {
25 | width: '100%',
26 | color: theme.palette.primary.dark,
27 | borderRadius: 0,
28 | fontWeight: 700,
29 | '&:hover': {
30 | color: 'white',
31 | backgroundColor: theme.palette.primary.dark,
32 | border: '1px solid rgba(0, 0, 0, 0.23)',
33 | },
34 | [theme.breakpoints.up('md')]: {
35 | width: 'auto',
36 | margin: theme.spacing(2),
37 | },
38 | },
39 | }));
40 |
41 | function HostSensorButton({ children, href, outlined, onClick, ...props }) {
42 | const classes = useStyles(props);
43 | const variant = outlined ? 'outlined' : 'contained';
44 | const className = outlined ? classes.buttonOutlined : classes.buttonContained;
45 |
46 | return (
47 |
56 | );
57 | }
58 |
59 | HostSensorButton.propTypes = {
60 | children: PropTypes.oneOfType([
61 | PropTypes.arrayOf(PropTypes.node),
62 | PropTypes.node,
63 | PropTypes.string,
64 | ]).isRequired,
65 | href: PropTypes.string,
66 | outlined: PropTypes.bool,
67 | onClick: PropTypes.func,
68 | };
69 | HostSensorButton.defaultProps = {
70 | href: undefined,
71 | outlined: false,
72 | onClick: undefined,
73 | };
74 |
75 | export default HostSensorButton;
76 |
--------------------------------------------------------------------------------
/src/components/PartnerLogos.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import worldbank from 'assets/images/partners/worldbankgroup.png';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | flexGrow: 1,
11 | backgroundColor: theme.palette.common.white,
12 | },
13 | title: {
14 | backgroundColor: 'white',
15 | padding: '2rem 0',
16 | [theme.breakpoints.down('md')]: {
17 | paddingTop: '3rem',
18 | },
19 | },
20 | partnersLogo: {
21 | padding: '2rem 0',
22 | },
23 | worldBankLogo: {
24 | width: '100%',
25 | textAlign: 'center',
26 | padding: '2rem 0',
27 | },
28 | liquidLogo: {
29 | width: '100%',
30 | textAlign: 'center',
31 | padding: '2rem 0',
32 | },
33 | globalLogo: {
34 | width: '100%',
35 | textAlign: 'center',
36 | paddingBottom: '2rem',
37 | },
38 | germanCoopLogo: {
39 | width: '100%',
40 | textAlign: 'center',
41 | paddingTop: 0,
42 | paddingBottom: '2rem',
43 | },
44 | img: {
45 | maxWidth: '90%',
46 | height: '100px',
47 | filter: 'grayscale(100%)',
48 | },
49 | }));
50 |
51 | function PartnerLogos() {
52 | const classes = useStyles();
53 | return (
54 |
55 |
63 | OUR PARTNERS
64 |
65 |
66 |
73 |
74 |
75 |

76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
83 | export default PartnerLogos;
84 |
--------------------------------------------------------------------------------
/src/theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from '@material-ui/core/styles';
2 |
3 | const FONT_FAMILY = '"Montserrat", "sans-serif"';
4 | const HEADINGS_FONT_FAMILY = '"Anton", "sans-serif"';
5 |
6 | const theme = createMuiTheme({
7 | // Green palette
8 | palette: {
9 | primary: { main: '#1a995b', light: '#2FB56B', dark: '#164B3E' },
10 | secondary: { main: '#424143', dark: '#2A2A2B' },
11 | },
12 | typography: {
13 | fontFamily: FONT_FAMILY,
14 | fontSize: 16,
15 | h1: {
16 | color: '#424143',
17 | fontFamily: HEADINGS_FONT_FAMILY,
18 | fontWeight: 500,
19 | textTransform: 'uppercase',
20 | },
21 | h2: {
22 | color: '#424143',
23 | fontFamily: HEADINGS_FONT_FAMILY,
24 | fontWeight: 500,
25 | textTransform: 'uppercase',
26 | },
27 | h3: {
28 | color: '#424143',
29 | fontFamily: HEADINGS_FONT_FAMILY,
30 | fontWeight: 500,
31 | textTransform: 'uppercase',
32 | },
33 | h4: {
34 | color: '#424143',
35 | fontFamily: HEADINGS_FONT_FAMILY,
36 | fontWeight: 500,
37 | textTransform: 'uppercase',
38 | },
39 | h5: {
40 | color: '#424143',
41 | fontFamily: HEADINGS_FONT_FAMILY,
42 | fontWeight: 500,
43 | textTransform: 'uppercase',
44 | },
45 | h6: {
46 | color: '#424143',
47 | fontFamily: FONT_FAMILY,
48 | fontWeight: 500,
49 | textTransform: 'uppercase',
50 | },
51 | fontSmallDefault: {
52 | fontSize: 14,
53 | },
54 | buttonNext: {
55 | fontWeight: 700,
56 | },
57 | useNextVariants: true,
58 | },
59 | overrides: {
60 | MuiBadge: {
61 | badge: {
62 | color: '#bbb',
63 | fontFamily: HEADINGS_FONT_FAMILY,
64 | fontWeight: 500,
65 | textTransform: 'uppercase',
66 | borderRadius: 0,
67 | },
68 | },
69 | MuiButton: {
70 | root: {
71 | borderRadius: 0,
72 | },
73 | },
74 | MuiCard: {
75 | root: {
76 | borderRadius: 0,
77 | boxShadow: 'none',
78 | },
79 | },
80 | MuiPaper: {
81 | root: {
82 | backgroundColor: '#F3F3F3',
83 | },
84 | },
85 | },
86 | });
87 |
88 | export default theme;
89 |
--------------------------------------------------------------------------------
/src/components/Air/Issues.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import HealthEffects from './HealthEffects';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | flexGrow: 1,
11 | paddingBottom: '2rem',
12 | paddingTop: '2rem',
13 | backgroundColor: 'white',
14 | },
15 | subheading: {
16 | paddingTop: '1rem',
17 | paddingBottom: '2rem',
18 | textAlign: 'center',
19 | fontWeight: 'bold',
20 | fontSize: 14,
21 | },
22 | issues: {
23 | textAlign: 'center',
24 | paddingBottom: '1rem',
25 | },
26 | title: {
27 | textAlign: 'center',
28 | textTransform: 'none',
29 | },
30 | caption: {
31 | paddingTop: '1rem',
32 | textAlign: 'center',
33 | },
34 | graph: {
35 | margin: '0 auto',
36 | width: '9rem',
37 | [theme.breakpoints.up('md')]: {
38 | width: '12rem',
39 | padding: '0 1rem',
40 | },
41 | [theme.breakpoints.up('lg')]: {
42 | width: '15rem',
43 | },
44 | },
45 | }));
46 |
47 | function Issues() {
48 | const classes = useStyles();
49 | return (
50 |
56 |
57 |
58 | THE ISSUES
59 |
60 |
61 |
62 |
63 | Air pollution causes 1 in 9 deaths
64 |
65 |
66 |
67 |
68 | and is the biggest environmental health crisis we face
69 |
70 |
71 |
72 |
73 | While it’s not always visible, air pollution is the cause of some of
74 | our most common illnesses.
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 |
85 | export default Issues;
86 |
--------------------------------------------------------------------------------
/src/components/Hambuger/MenuButton.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { withStyles } from '@material-ui/core/styles';
5 |
6 | const styles = {
7 | container: {
8 | height: 32,
9 | width: 32,
10 | display: 'flex',
11 | flexDirection: 'column',
12 | justifyContent: 'center',
13 | alignItems: 'center',
14 | cursor: 'pointer',
15 | padding: 4,
16 | zIndex: '1301',
17 | position: 'relative',
18 | },
19 | };
20 |
21 | class MenuButton extends Component {
22 | static getDerivedStateFromProps(nextProps, prevState) {
23 | if (nextProps.open !== prevState.open) {
24 | return { open: nextProps.open };
25 | }
26 | return null;
27 | }
28 |
29 | constructor(props) {
30 | super(props);
31 |
32 | const { open } = props;
33 | this.state = { open };
34 | }
35 |
36 | render() {
37 | const { classes, color, onClick } = this.props;
38 | const { open } = this.state;
39 | const dynamicStyles = {
40 | line: {
41 | height: 4,
42 | width: 28,
43 | background: color,
44 | transition: 'all 0.2s ease',
45 | },
46 | lineTop: {
47 | transform: open ? 'rotate(45deg)' : 'none',
48 | marginBottom: open ? '0' : '4px',
49 | },
50 | lineMiddle: {
51 | opacity: open ? 0 : 1,
52 | },
53 | lineBottom: {
54 | transform: open ? 'rotate(-45deg)' : 'none',
55 | marginTop: open ? '-8px' : '4px',
56 | },
57 | };
58 | return (
59 |
70 | );
71 | }
72 | }
73 |
74 | MenuButton.propTypes = {
75 | color: PropTypes.string,
76 | onClick: PropTypes.func,
77 | open: PropTypes.bool,
78 | };
79 | MenuButton.defaultProps = {
80 | color: 'white',
81 | onClick: null,
82 | open: false,
83 | };
84 | export default withStyles(styles)(MenuButton);
85 |
--------------------------------------------------------------------------------
/src/components/Showcase/StoryCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | Typography,
5 | Card,
6 | CardActionArea,
7 | CardMedia,
8 | CardContent,
9 | } from '@material-ui/core';
10 | import { makeStyles } from '@material-ui/core/styles';
11 |
12 | const useStyles = makeStyles((theme) => ({
13 | root: {
14 | width: '100vw',
15 | height: '100%',
16 | backgroundColor: '#fafafa',
17 | border: '1px solid #eeeeee',
18 | maxWidth: '100%',
19 | opacity: 0.9,
20 | '&:hover': {
21 | opacity: 1,
22 | backgroundColor: '#fff',
23 | },
24 | [theme.breakpoints.up('md')]: {
25 | width: '19.875rem',
26 | },
27 | [theme.breakpoints.up('lg')]: {
28 | width: '26.5rem',
29 | },
30 | },
31 | media: {
32 | height: 0,
33 | paddingTop: '56.25%',
34 | width: '100%',
35 | },
36 | cardLink: {
37 | textDecoration: 'none',
38 | },
39 | overline: {
40 | color: '#c7c7c7',
41 | opacity: '0.5',
42 | fontSize: '14px',
43 | paddingTop: '1rem',
44 | },
45 | body: {
46 | color: theme.typography.h5.color,
47 | },
48 | bodyArea: {
49 | paddingTop: '2rem',
50 | },
51 | }));
52 |
53 | function StoryCard({ story }) {
54 | const classes = useStyles();
55 | const { image, date, title, body, link } = story;
56 |
57 | return (
58 |
59 |
65 |
68 |
69 |
70 |
71 | {date}
72 |
73 |
74 | {title}
75 |
76 | {body}{' '}
77 |
78 |
79 |
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default StoryCard;
87 |
--------------------------------------------------------------------------------
/src/components/TimeSeries/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | root: {
9 | margin: '0 auto',
10 | },
11 | section: {
12 | margin: '0 auto',
13 | width: '100%',
14 | [theme.breakpoints.up('md')]: {
15 | width: '59.625rem',
16 | },
17 | [theme.breakpoints.up('lg')]: {
18 | width: '76.125rem',
19 | },
20 | [theme.breakpoints.down('sm')]: {
21 | width: '100vw',
22 | },
23 | },
24 | ticker: {
25 | border: '1px solid #D6D6D6',
26 | boxShadow: '0px 4px 4px #00000029',
27 | marginTop: '1rem',
28 | padding: '1.125rem',
29 | [theme.breakpoints.up('md')]: {
30 | padding: '1.125rem 2.625rem 1.25rem',
31 | },
32 | },
33 | subtitle: {
34 | color: '#5D5C5C',
35 | fontFamily: theme.typography.h3.fontFamily,
36 | textTransform: 'uppercase',
37 | },
38 | chartContainer: {
39 | marginTop: '1.125rem',
40 | },
41 | chartStyles: {
42 | border: 0,
43 | width: '100%',
44 | height: '40vh',
45 | pointerEvents: 'none',
46 | },
47 | }));
48 |
49 | function TimeSeries({ description, ...props }) {
50 | const classes = useStyles(props);
51 |
52 | return (
53 |
54 |
55 |
61 |
62 |
67 |
68 |
69 | {description}
70 |
71 |
72 |
73 |
74 | );
75 | }
76 |
77 | TimeSeries.propTypes = {
78 | description: PropTypes.string,
79 | };
80 |
81 | TimeSeries.defaultProps = {
82 | description: undefined,
83 | };
84 |
85 | export default TimeSeries;
86 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AirCityHeaderContent/NeedlePointer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | import React from 'react';
3 |
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import Proptypes from 'prop-types';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | pointer: { fill: 'rgb(20, 74, 61)' },
10 | gaugeBigText: {
11 | fontFamily: theme.typography.h6.fontFamily,
12 | fontSize: theme.typography.h6.fontSize,
13 | fontWeight: 700,
14 | fill: theme.palette.primary.dark,
15 | },
16 | hidden: {
17 | visibility: 'hidden',
18 | },
19 | }));
20 |
21 | function NeedlePointer({ hidden, measurement }) {
22 | const classes = useStyles();
23 | let rotate = -90;
24 | const className = hidden ? classes.hidden : '';
25 |
26 | // Limit the value to 160
27 | const value = measurement > 160 ? 160 : measurement;
28 | if (value > 60) {
29 | rotate = ((value - 60) * 45) / 100 + 45;
30 | } else {
31 | rotate = (value * (45 + 90)) / 60 + -90;
32 | }
33 |
34 | return (
35 |
36 |
44 | 55 ? 5 : -2.5)
49 | }`}
50 | y={`${-275 * Math.sin(((rotate + 90) * Math.PI) / 180)}`}
51 | textAnchor={
52 | measurement > 25 && measurement < 55
53 | ? 'middle'
54 | : measurement > 40
55 | ? 'start'
56 | : 'end'
57 | }
58 | >
59 |
60 | {Math.round(measurement * 10) / 10}
61 |
62 |
63 |
64 | );
65 | }
66 |
67 | NeedlePointer.propTypes = {
68 | hidden: Proptypes.bool.isRequired,
69 | measurement: Proptypes.number.isRequired,
70 | };
71 |
72 | export default NeedlePointer;
73 |
--------------------------------------------------------------------------------
/src/components/Ticker/Status.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography, useMediaQuery } from '@material-ui/core';
5 | import { makeStyles, useTheme } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | root: {
9 | flexGrow: 1,
10 | color: theme.palette.primary.main,
11 | textAlign: 'center',
12 | },
13 | name: {
14 | color: 'inherit',
15 | },
16 | status: {
17 | color: 'inherit',
18 | },
19 | value: {
20 | color: 'inherit',
21 | lineHeight: '6.25rem',
22 | fontSize: '3rem',
23 | },
24 | valueText: {
25 | color: '#9D9C9C',
26 | fontFamily: theme.typography.h3.fontFamily,
27 | },
28 | }));
29 |
30 | function Status({ name, status, value, valueText, ...props }) {
31 | const classes = useStyles(props);
32 | const theme = useTheme();
33 | const isTablet = useMediaQuery(theme.breakpoints.only('md'));
34 | const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
35 |
36 | return (
37 |
38 |
39 |
44 | {status}
45 |
46 |
47 |
48 |
53 | {name}
54 |
55 |
56 |
57 |
61 | {value.toLocaleString()}
62 |
63 | {valueText?.length ? (
64 |
69 | {valueText}
70 |
71 | ) : null}
72 |
73 |
74 | );
75 | }
76 |
77 | Status.propTypes = {
78 | name: PropTypes.string.isRequired,
79 | status: PropTypes.string.isRequired,
80 | value: PropTypes.number.isRequired,
81 | valueText: PropTypes.string,
82 | };
83 |
84 | Status.defaultProps = {
85 | valueText: undefined,
86 | };
87 |
88 | export default Status;
89 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/HostSensorButtons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 | // import { HashLink } from 'react-router-hash-link';
7 |
8 | import HostSensorButton from 'components/City/HostSensors/HostSensorButton';
9 | import ShareButton from 'components/City/HostSensors/ShareButton';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | root: {
13 | flexGrow: 1,
14 | paddingTop: '2rem',
15 | },
16 | button: {
17 | margin: '0.25rem 1rem',
18 | width: '100%',
19 | [theme.breakpoints.up('md')]: {
20 | margin: 0,
21 | width: 'auto',
22 | },
23 | },
24 | buttonLink: {
25 | display: 'block',
26 | width: '100%',
27 | textDecoration: 'none',
28 | [theme.breakpoints.up('md')]: {
29 | display: 'inline-block',
30 | width: 'auto',
31 | },
32 | },
33 | }));
34 |
35 | function HostSensorButtons({ city }) {
36 | const classes = useStyles();
37 | return (
38 |
44 |
45 |
51 | SUBSCRIBE
52 |
53 |
54 |
55 |
61 | CONNECT
62 |
63 |
64 |
65 | EXPLORE
66 |
67 |
68 | SHARE
69 |
70 |
71 | );
72 | }
73 |
74 | HostSensorButtons.propTypes = {
75 | city: PropTypes.shape({}).isRequired,
76 | };
77 |
78 | export default HostSensorButtons;
79 |
--------------------------------------------------------------------------------
/src/components/Header/JumboContent/AirCityHeaderContent/DigitalGauge.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles({
8 | root: {
9 | flexGrow: 1,
10 | },
11 | dial: {
12 | textAlign: 'center',
13 | },
14 | measurement: {
15 | width: '16rem',
16 | padding: '0.75rem 0',
17 | color: 'white',
18 | textAlign: 'center',
19 | borderBottom: '1px solid white',
20 | },
21 | measurementUnit: {
22 | width: '16rem',
23 | color: 'white',
24 | textAlign: 'center',
25 | textTransform: 'uppercase',
26 | },
27 | safeLevel: {
28 | width: '16rem',
29 | padding: '0.75rem 0',
30 | color: 'white',
31 | textAlign: 'center',
32 | textTransform: 'uppercase',
33 | fontWeight: 'bold',
34 | },
35 | });
36 |
37 | function DigitalGauge({ airPollMeasurement, airPollDescription }) {
38 | const classes = useStyles();
39 | return (
40 |
46 |
54 |
55 |
60 | {airPollMeasurement}
61 | {airPollMeasurement !== '--' && (
62 |
67 | PM
68 | 2.5 24 Hours Exposure
69 |
70 | )}
71 |
72 |
77 | {airPollDescription}
78 |
79 |
80 |
81 |
82 | );
83 | }
84 |
85 | DigitalGauge.propTypes = {
86 | airPollMeasurement: PropTypes.string.isRequired,
87 | airPollDescription: PropTypes.string.isRequired,
88 | };
89 |
90 | export default DigitalGauge;
91 |
--------------------------------------------------------------------------------
/src/components/Air/GaugeChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classNames from 'classnames';
5 |
6 | import { makeStyles } from '@material-ui/core/styles';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | display: 'block',
11 | maxWidth: '100%',
12 | margin: 'auto',
13 | },
14 | background: {
15 | stroke: 'white',
16 | },
17 | stroke: {
18 | fill: 'none',
19 | stroke: theme.palette.primary.light,
20 | strokeWidth: '7.4',
21 | },
22 | readingBackground: {
23 | fill: 'none',
24 | stroke: 'white',
25 | // anything less than readingStroke's strokeWidth to make sure there isn't
26 | // any visible white gap between the two
27 | strokeWidth: '4.5',
28 | },
29 | readingStroke: {
30 | fill: 'none',
31 | stroke: theme.palette.primary.light,
32 | strokeWidth: '4.8',
33 | animation: 'progress 1s ease-out forwards',
34 | },
35 | label: {
36 | fill: '#666',
37 | stroke: '#666',
38 | strokeWidth: '0.25',
39 | fontFamily: theme.typography.fontFamily,
40 | fontWeight: 600,
41 | fontSize: '0.35em',
42 | textAnchor: 'middle',
43 | },
44 | '@keyframes progress': {
45 | from: {
46 | strokeDashoffset: '100',
47 | },
48 | to: {
49 | strokeDashoffset: '200',
50 | },
51 | },
52 | }));
53 |
54 | function GaugeChart({ percentage, ...props }) {
55 | const classes = useStyles(props);
56 |
57 | return (
58 |
85 | );
86 | }
87 |
88 | GaugeChart.propTypes = {
89 | percentage: PropTypes.number.isRequired,
90 | };
91 |
92 | export default GaugeChart;
93 |
--------------------------------------------------------------------------------
/public/favicons/blue/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/public/favicons/black/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/public/favicons/green/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/public/favicons/orange/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/public/favicons/purple/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
42 |
--------------------------------------------------------------------------------
/src/assets/css/App.css:
--------------------------------------------------------------------------------
1 | /* App css */
2 | .list-ic li {
3 | position: relative;
4 | }
5 | .list-ic li span {
6 | display: inline-block;
7 | font-weight: 800;
8 | width: 1em;
9 | height: 1em;
10 | text-align: center;
11 | line-height: 3em;
12 | border-radius: 50%;
13 | background: #18bc9c;
14 | position: relative;
15 | }
16 | .list-ic li::before {
17 | content: '';
18 | position: absolute;
19 | background: rgba(192, 192, 192, 0.9);
20 | }
21 | .list-ic.vertical {
22 | padding: 0;
23 | margin: 0;
24 | }
25 | .list-ic.vertical li {
26 | list-style-type: none;
27 | text-align: left;
28 | }
29 | .list-ic.vertical li::before {
30 | top: -25px;
31 | left: 31px;
32 | width: 0.1em;
33 | height: 10em;
34 | visibility: visible;
35 | }
36 |
37 | .card {
38 | position: relative;
39 | background: white;
40 | margin-left: 30px;
41 | margin-left: 3rem;
42 | height: 6.5rem;
43 | width: 28rem;
44 | border-radius: none;
45 | border: 1px solid black;
46 | }
47 | .card:after,
48 | .card:before {
49 | content: '';
50 | position: absolute;
51 | right: 100%;
52 | top: 50%;
53 | width: 0;
54 | height: 0;
55 | }
56 | .card:after {
57 | border-top: 20px solid transparent;
58 | border-right: 20px solid white;
59 | border-bottom: 20px solid transparent;
60 | border-left: 20px solid transparent;
61 | margin-top: -20px;
62 | }
63 | .card:before {
64 | border-top: 21px solid transparent;
65 | border-right: 21px solid black;
66 | border-bottom: 21px solid transparent;
67 | border-left: 21px solid transparent;
68 | margin-top: -21px;
69 | }
70 |
71 | .Email-input input {
72 | width: 100%;
73 | background-color: #fff;
74 | border: 1px solid #fff;
75 | text-align: center;
76 | height: 2rem;
77 | font-size: 15px;
78 | }
79 |
80 | .segment-value {
81 | font-family: 'Montserrat', 'sans-serif';
82 | }
83 |
84 | .speedo-segment:nth-of-type(1) {
85 | fill: rgb(95, 191, 130);
86 | }
87 |
88 | .speedo-segment:nth-of-type(2) {
89 | fill: rgb(52, 184, 111);
90 | }
91 |
92 | .speedo-segment:nth-of-type(3) {
93 | fill: rgb(41, 154, 92);
94 | }
95 |
96 | .speedo-segment:nth-of-type(4) {
97 | fill: rgb(206, 142, 78);
98 | }
99 |
100 | .speedo-segment:nth-of-type(5) {
101 | fill: rgb(207, 125, 78);
102 | }
103 |
104 | .speedo-segment:nth-of-type(6) {
105 | fill: rgb(212, 95, 75);
106 | }
107 |
108 | .speedo-segment:nth-of-type(7) {
109 | fill: rgb(206, 76, 52);
110 | }
111 |
112 | .speedo-segment:nth-of-type(8) {
113 | fill: rgb(183, 32, 37);
114 | }
115 |
116 | .segment-value:nth-of-type(1) {
117 | display: none;
118 | }
119 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/ImpactCards.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, CardContent, Typography } from '@material-ui/core';
3 | import PropTypes from 'prop-types';
4 | import { withStyles } from '@material-ui/core/styles';
5 |
6 | let lightBackgroundColor;
7 | let darkBackgroundColor;
8 |
9 | const styles = (theme) => {
10 | lightBackgroundColor = theme.palette.primary.light;
11 | darkBackgroundColor = theme.palette.primary.dark;
12 |
13 | return {
14 | root: {
15 | textAlign: 'center',
16 | width: '100vw',
17 | backgroundColor: theme.palette.primary.light,
18 | [theme.breakpoints.up('md')]: {
19 | width: '25rem',
20 | height: '36.75rem',
21 | padding: '2rem',
22 | },
23 | [theme.breakpoints.up('lg')]: {
24 | width: '37.875rem',
25 | height: '36.75rem',
26 | padding: '3rem',
27 | },
28 | },
29 | img: {
30 | height: '5rem',
31 | maxWidth: '100%',
32 | [theme.breakpoints.up('md')]: {
33 | height: '100%',
34 | },
35 | },
36 | title: {
37 | color: '#fff',
38 | textAlign: 'center',
39 | paddingTop: '1.5rem',
40 | paddingBottom: '1.5rem',
41 | [theme.breakpoints.up('lg')]: {
42 | fontSize: theme.typography.h4.fontSize,
43 | fontWeight: theme.typography.h4.fontWeight,
44 | },
45 | },
46 | caption: {
47 | textAlign: 'center',
48 | color: '#F3F3F3',
49 | paddingBottom: '1rem',
50 | [theme.breakpoints.up('lg')]: {
51 | fontSize: theme.typography.subtitle1.fontSize,
52 | fontWeight: theme.typography.subtitle1.fontWeight,
53 | },
54 | },
55 | };
56 | };
57 |
58 | function PollutionBurden({ classes, icon, title, impact, dark }) {
59 | const backgroundColor = dark ? darkBackgroundColor : lightBackgroundColor;
60 | return (
61 |
62 |
63 |
64 |
65 | {title}
66 |
67 |
68 | {impact}
69 |
70 |
71 |
72 | );
73 | }
74 |
75 | PollutionBurden.propTypes = {
76 | icon: PropTypes.string.isRequired,
77 | title: PropTypes.string.isRequired,
78 | impact: PropTypes.string.isRequired,
79 | dark: PropTypes.bool,
80 | };
81 |
82 | PollutionBurden.defaultProps = {
83 | dark: false,
84 | };
85 |
86 | export default withStyles(styles)(PollutionBurden);
87 |
--------------------------------------------------------------------------------
/src/components/Favicon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Helmet } from 'react-helmet';
4 |
5 | const CONTENT = {
6 | black: {
7 | hex: '#424143',
8 | version: 'Ewqar9pC28',
9 | },
10 | blue: {
11 | hex: '#4972b8',
12 | version: '8wB2COTulV',
13 | },
14 | green: {
15 | hex: '#2fb56b',
16 | version: '78bppvKdQd',
17 | },
18 | orange: {
19 | hex: '#F57C00',
20 | version: 'WxbPiUQG57',
21 | },
22 | purple: {
23 | hex: '#b64598',
24 | version: 'WxbPiUQG57',
25 | },
26 | };
27 |
28 | /**
29 | manifest.json provides metadata used when your web app is added to the
30 | homescreen on Android.
31 | See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
32 | */
33 | function Favicon({ color, version }) {
34 | const { hex, version: defaultVersion } = CONTENT[color];
35 | const v = version || defaultVersion;
36 |
37 | return (
38 |
86 | );
87 | }
88 |
89 | Favicon.propTypes = {
90 | color: PropTypes.string,
91 | version: PropTypes.string,
92 | };
93 |
94 | Favicon.defaultProps = {
95 | color: 'black',
96 | version: null,
97 | };
98 |
99 | export default Favicon;
100 |
--------------------------------------------------------------------------------
/src/components/City/AQIndex.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import { Grid, Typography } from '@material-ui/core';
4 |
5 | const useStyles = makeStyles(() => ({
6 | root: {
7 | marginTop: '2.5rem',
8 | },
9 | marginStyle: {
10 | margin: '0.625rem 0.313rem',
11 | },
12 | textStyle: {
13 | marginLeft: '0.313rem',
14 | display: 'inline',
15 | },
16 | }));
17 |
18 | function AQIndex() {
19 | const classes = useStyles();
20 |
21 | return (
22 |
23 | AQI Index
24 |
25 |
26 |
29 |
30 | Good
31 |
32 |
33 |
34 |
37 |
38 | Moderate
39 |
40 |
41 |
42 |
45 |
46 | Unhealthy for Sensitive Groups
47 |
48 |
49 |
50 |
53 |
54 | Unhealthy
55 |
56 |
57 |
58 |
61 |
62 | Very Unhealthy
63 |
64 |
65 |
66 |
69 |
70 | Hazardous
71 |
72 |
73 |
74 |
75 | );
76 | }
77 |
78 | export default AQIndex;
79 |
--------------------------------------------------------------------------------
/src/components/Email.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { Button, Grid, Input, FormControl } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | flexGrow: 1,
9 | color: 'white',
10 | background: 'none',
11 | },
12 | footerButton: {
13 | width: '100%',
14 | color: 'white',
15 | '&:hover': {
16 | color: theme.palette.secondary.main,
17 | },
18 | backgroundColor: theme.palette.secondary.dark,
19 | fontWeight: 800,
20 | fontSize: theme.typography.subtitle2.fontSize,
21 | height: '3rem',
22 | [theme.breakpoints.up('lg')]: {
23 | fontSize: theme.typography.subtitle1.fontSize,
24 | height: '3.5rem',
25 | paddingLeft: '2rem',
26 | paddingRight: '2rem',
27 | },
28 | },
29 | buttonContainer: {
30 | paddingTop: '1rem',
31 | },
32 | footerInput: {
33 | // Moved to `App.css` due to difficult of setting input `text-align` to `center`
34 | },
35 | buttonLink: {
36 | textDecoration: 'none',
37 | },
38 | }));
39 |
40 | function Email(props) {
41 | const classes = useStyles(props);
42 | const [value, setValue] = useState('');
43 | const handleChange = (e) => setValue(e.target.value);
44 |
45 | return (
46 |
52 |
53 |
86 |
87 |
88 | );
89 | }
90 |
91 | export default Email;
92 |
--------------------------------------------------------------------------------
/src/components/DocumentHead/PageHeads.js:
--------------------------------------------------------------------------------
1 | const URLS = {
2 | HOME: '/',
3 | ABOUT: '/about',
4 |
5 | AIR: {
6 | HOME: '/air',
7 | ABOUT: '/air/about',
8 | DATA: '/air/data',
9 | // Redirect /air/city to /air/city/nairobi
10 | CITY_HOME: '/air/city',
11 | CITY_DEFAULT: '/air/city/nairobi',
12 | CITY_DSM: '/air/city/dar-es-salaam',
13 | CITY_LAGOS: '/air/city/lagos',
14 | CITY_NAIROBI: '/air/city/nairobi',
15 | HOW_SENSORS_WORK: '/air/how-sensors-work',
16 | IMPACT: '/air/health-and-climate-impact',
17 | JOIN: '/air/join-network',
18 | },
19 | WATER: {
20 | HOME: '/water',
21 | },
22 | SOUND: {
23 | HOME: '/sound',
24 | },
25 | RADIATION: {
26 | HOME: '/radiation',
27 | },
28 | NOT_FOUND: '*',
29 | };
30 |
31 | export { URLS };
32 |
33 | export default [
34 | {
35 | url: URLS.HOME,
36 | title: 'sensors.AFRICA | Home',
37 | color: 'black',
38 | },
39 | {
40 | url: URLS.ABOUT,
41 | title: 'sensors.AFRICA | ABOUT',
42 | color: 'black',
43 | },
44 | {
45 | url: URLS.AIR.DATA,
46 | title: 'sensors.AFRICA | Air | DATA',
47 | color: 'green',
48 | },
49 | {
50 | url: URLS.AIR.HOME,
51 | title: 'sensors.AFRICA | Air | Home',
52 | color: 'green',
53 | },
54 | {
55 | url: URLS.AIR.ABOUT,
56 | title: 'sensors.AFRICA | Air | About',
57 | color: 'green',
58 | },
59 | {
60 | url: URLS.AIR.JOIN,
61 | title: 'sensors.AFRICA | Air | Join Network',
62 | color: 'green',
63 | },
64 | {
65 | url: URLS.AIR.HOW_SENSORS_WORK,
66 | title: 'sensors.AFRICA | Air | How Sensors Work',
67 | color: 'green',
68 | },
69 | {
70 | url: URLS.AIR.IMPACT,
71 | title: 'sensors.AFRICA | Air | Health and Climate Impacts',
72 | color: 'green',
73 | },
74 | {
75 | url: URLS.AIR.CITY_DSM,
76 | title: 'sensors.AFRICA | Air | Dar es Salaam',
77 | color: 'green',
78 | },
79 | {
80 | url: URLS.AIR.CITY_LAGOS,
81 | title: 'sensors.AFRICA | Air | Lagos',
82 | color: 'green',
83 | },
84 | {
85 | url: URLS.AIR.CITY_NAIROBI,
86 | title: 'sensors.AFRICA | Air | Nairobi',
87 | color: 'green',
88 | },
89 | {
90 | url: URLS.RADIATION.HOME,
91 | title: 'sensors.AFRICA | Radiation | Coming Soon',
92 | color: 'orange',
93 | },
94 | {
95 | url: URLS.SOUND.HOME,
96 | title: 'sensors.AFRICA | Sound | Coming Soon',
97 | color: 'purple',
98 | },
99 | {
100 | url: URLS.WATER.HOME,
101 | title: 'sensors.AFRICA | Water | Coming Soon',
102 | color: 'blue',
103 | },
104 | {
105 | url: URLS.NOT_FOUND,
106 | title: 'sensors.AFRICA | Page Not Found',
107 | color: 'black',
108 | },
109 | ];
110 |
--------------------------------------------------------------------------------
/src/components/City/SensorsQualityStats/StatsSummary.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button, Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | flexGrow: 1,
9 | [theme.breakpoints.down('lg')]: {
10 | paddingBottom: '3rem',
11 | },
12 | },
13 | button: {
14 | backgroundColor: theme.palette.primary.dark,
15 | color: '#fff',
16 | height: 50,
17 | width: 100,
18 | borderRadius: 0,
19 | },
20 | container: {
21 | textAlign: 'center',
22 | paddingTop: '0.4rem',
23 | },
24 | buttonConnectText: {
25 | margin: '2rem',
26 | },
27 | display2: {
28 | color: theme.palette.primary.light,
29 | },
30 | caption: {
31 | textAlign: 'center',
32 | },
33 | small: {
34 | fontSize: '1.5rem',
35 | },
36 | }));
37 |
38 | function StatsSummary() {
39 | const classes = useStyles();
40 | return (
41 |
52 |
60 |
63 | to
64 |
67 |
68 |
69 |
70 |
74 | Sensor's Data
75 |
76 |
82 |
83 | 1,234
84 |
85 | Ug/m
86 | 3
87 |
88 |
89 |
90 |
91 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dapibus
92 | dui nec ligula semper eleifend. Quisque rhoncus tortor
93 |
94 |
95 |
96 | );
97 | }
98 |
99 | export default StatsSummary;
100 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/PollutionSource.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Grid, Typography } from '@material-ui/core';
3 | import PropTypes from 'prop-types';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | root: {
8 | flex: 1,
9 | padding: '2rem',
10 | },
11 | mainTitle: {
12 | color: theme.palette.primary.light,
13 | textAlign: 'center',
14 | fontSize: theme.typography.h6.fontSize,
15 | },
16 | subTitle: {
17 | textAlign: 'center',
18 | fontWeight: 600,
19 | },
20 | body: {
21 | paddingTop: '1.5rem',
22 | },
23 | bodyCopy: {
24 | textAlign: 'center',
25 | },
26 | }));
27 | function PollutionSource({
28 | title,
29 | sources,
30 | impact,
31 | reduction,
32 | backgroundColor,
33 | }) {
34 | const classes = useStyles();
35 | return (
36 |
43 |
44 |
45 | {title}
46 |
47 |
48 |
49 |
50 |
51 | Sources:
52 |
53 |
54 |
55 |
56 | {sources}
57 |
58 |
59 |
60 |
61 |
62 | Impact:
63 |
64 |
65 |
66 |
67 | {impact}
68 |
69 |
70 |
71 |
72 |
73 | Reduction:
74 |
75 |
76 |
77 |
78 | {reduction}
79 |
80 |
81 |
82 | );
83 | }
84 |
85 | PollutionSource.propTypes = {
86 | title: PropTypes.string.isRequired,
87 | sources: PropTypes.string.isRequired,
88 | impact: PropTypes.string.isRequired,
89 | reduction: PropTypes.string.isRequired,
90 | backgroundColor: PropTypes.string,
91 | };
92 |
93 | PollutionSource.defaultProps = {
94 | backgroundColor: '#F3F3F3',
95 | };
96 |
97 | export default PollutionSource;
98 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/HostCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Card, CardContent, Typography, Button } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | findOutMore: {
8 | color: theme.palette.primary.dark,
9 | fontSize: theme.typography.fontSize,
10 | fontWeight: 'bold',
11 | },
12 | card: {
13 | height: '38rem',
14 | width: '100%',
15 | backgroundColor: theme.palette.primary.light,
16 | borderRadius: 0,
17 | },
18 | cardContent: {
19 | textAlign: 'center',
20 | marginTop: '1rem',
21 | },
22 | display1: {
23 | color: '#fff',
24 | paddingTop: '2rem',
25 | },
26 | body2: {
27 | color: '#fff',
28 | paddingTop: theme.spacing(4),
29 | },
30 | caption: {
31 | color: '#fff',
32 | paddingTop: '2rem',
33 | textDecoration: 'underline',
34 | },
35 | cardButtonOutlined: {
36 | paddingTop: theme.spacing(3),
37 | },
38 | buttonLink: {
39 | textDecoration: 'none',
40 | },
41 | }));
42 |
43 | function HostCard() {
44 | const classes = useStyles();
45 | return (
46 |
47 |
48 |
49 | HOST A SENSORS
50 |
51 | CALL TO ACTION
52 |
53 |
54 |
55 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in
56 | ultrices ex. Duis aliquet sapien nec dui laoreet mattis
57 |
58 |
59 |
60 |
64 | Local Government
65 |
66 |
67 |
68 |
72 | Environment Ministry
73 |
74 |
75 |
81 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 |
95 | export default HostCard;
96 |
--------------------------------------------------------------------------------
/src/components/HealthClimate/HealthAndClimateImpact.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import ImpactCard from 'components/HealthClimate/ImpactCards';
7 |
8 | import HealthImpact from 'assets/images/healthimpacticon.png';
9 | import ClimateImpact from 'assets/images/climateimpacticon.png';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | root: {
13 | flexGrow: 1,
14 | paddingTop: '3rem',
15 | backgroundColor: 'white',
16 | [theme.breakpoints.up('md')]: {
17 | paddingBottom: '3rem',
18 | },
19 | },
20 | health: {
21 | [theme.breakpoints.up('md')]: {
22 | marginRight: '4.8125rem',
23 | },
24 | [theme.breakpoints.up('lg')]: {
25 | marginRight: '1.875rem',
26 | },
27 | },
28 | climate: {
29 | [theme.breakpoints.up('md')]: {
30 | marginLeft: '4.8125rem',
31 | },
32 | [theme.breakpoints.up('lg')]: {
33 | marginLeft: '1.875rem',
34 | },
35 | },
36 | }));
37 |
38 | function HealthClimateCards() {
39 | const classes = useStyles();
40 | return (
41 |
47 |
48 |
49 |
63 |
64 |
65 |
66 |
67 |
68 |
80 |
81 |
82 |
83 | );
84 | }
85 |
86 | export default HealthClimateCards;
87 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wbsensors",
3 | "description": "WB-Sensors Dashboard",
4 | "version": "0.4.5",
5 | "private": true,
6 | "main": "index.js",
7 | "homepage": "https://codeforafrica.github.io/WB-sensors-Dashboard/",
8 | "repository": "https://github.com/CodeForAfrica/WB-sensors-Dashboard.git",
9 | "author": "Code For Africa",
10 | "license": "GNU GPLv3",
11 | "dependencies": {
12 | "@fortawesome/fontawesome-free": "^5.15.1",
13 | "@fortawesome/fontawesome-svg-core": "^1.2.32",
14 | "@fortawesome/free-brands-svg-icons": "^5.15.1",
15 | "@fortawesome/free-solid-svg-icons": "^5.15.1",
16 | "@fortawesome/react-fontawesome": "^0.1.12",
17 | "@material-ui/core": "^4.11.0",
18 | "classnames": "^2.2.6",
19 | "date-fns": "^2.17.0",
20 | "isomorphic-unfetch": "^3.1.0",
21 | "next": "^10.0.1",
22 | "next-auth": "^3.1.0",
23 | "next-images": "^1.6.2",
24 | "next-seo": "^4.15.0",
25 | "prop-types": "^15.7.2",
26 | "react": "^17.0.1",
27 | "react-dom": "^17.0.1",
28 | "react-feather": "^2.0.8",
29 | "react-helmet": "^6.1.0",
30 | "react-select": "^3.1.0",
31 | "react-share": "^4.3.1",
32 | "seed-color": "^2.0.1",
33 | "tabletop": "^1.6.3",
34 | "victory": "^35.3.3",
35 | "webpack": "^5.4.0"
36 | },
37 | "scripts": {
38 | "dev": "next",
39 | "dev:now": "now dev",
40 | "start": "next start",
41 | "build": "next build",
42 | "deploy": "now --prod",
43 | "lint": "yarn eslint --fix --ignore-path './.gitignore' --ext '.js,.json,.md' './'",
44 | "lint-staged": "yarn eslint --fix --ignore-path './.gitignore' --ext '.js,.json,.md'"
45 | },
46 | "devDependencies": {
47 | "@babel/core": "^7.12.3",
48 | "babel-eslint": "^10.1.0",
49 | "babel-plugin-module-resolver": "^4.0.0",
50 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
51 | "eslint": "^7.12.1",
52 | "eslint-config-airbnb": "^18.2.0",
53 | "eslint-config-prettier": "^6.15.0",
54 | "eslint-import-resolver-babel-module": "^5.2.0",
55 | "eslint-plugin-import": "^2.22.1",
56 | "eslint-plugin-json": "^2.1.2",
57 | "eslint-plugin-jsx-a11y": "^6.4.1",
58 | "eslint-plugin-markdown": "^1.0.2",
59 | "eslint-plugin-module-resolver": "^1.0.0",
60 | "eslint-plugin-prettier": "^3.1.4",
61 | "eslint-plugin-react": "^7.21.5",
62 | "eslint-plugin-react-hooks": "^4.2.0",
63 | "husky": "^4.3.0",
64 | "lint": "^0.7.0",
65 | "lint-staged": "^10.5.1",
66 | "now": "^17.0.4",
67 | "prettier": "^2.1.2"
68 | },
69 | "eslintConfig": {
70 | "extends": "react-app"
71 | },
72 | "browserslist": {
73 | "production": [
74 | ">0.2%",
75 | "not dead",
76 | "not op_mini all"
77 | ],
78 | "development": [
79 | "last 1 chrome version",
80 | "last 1 firefox version",
81 | "last 1 safari version"
82 | ]
83 | },
84 | "husky": {
85 | "hooks": {
86 | "pre-commit": "lint-staged"
87 | }
88 | },
89 | "lint-staged": {
90 | "src/**/*.{js,json,scss,md}": [
91 | "yarn lint-staged"
92 | ]
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/Chart/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Grid, Typography } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | root: {
9 | margin: '0 10px',
10 | },
11 | section: {
12 | margin: '0 10px',
13 | width: '100%',
14 | [theme.breakpoints.up('md')]: {
15 | width: '59.625rem',
16 | },
17 | [theme.breakpoints.up('lg')]: {
18 | width: '36.9rem',
19 | },
20 | [theme.breakpoints.down('sm')]: {
21 | width: '100vw',
22 | },
23 | },
24 | ticker: {
25 | border: '1px solid #D6D6D6',
26 | boxShadow: '0px 4px 4px #00000029',
27 | marginTop: '1rem',
28 | padding: '1.125rem',
29 | [theme.breakpoints.up('md')]: {
30 | padding: '1.125rem 2.625rem 1.25rem',
31 | },
32 | },
33 | subtitle: {
34 | color: '#5D5C5C',
35 | fontFamily: theme.typography.h3.fontFamily,
36 | textTransform: 'uppercase',
37 | },
38 | chartContainer: {
39 | marginTop: '1.125rem',
40 | },
41 | title: {
42 | textTransform: 'uppercase',
43 | },
44 | description: {
45 | marginTop: '30px',
46 | },
47 | chartStyles: {
48 | border: 0,
49 | width: '100%',
50 | height: '40vh',
51 | pointerEvents: 'none',
52 | },
53 | }));
54 |
55 | function Chart({ title, subtitle, description, chartSrc, ...props }) {
56 | const classes = useStyles(props);
57 |
58 | return (
59 |
60 |
61 |
67 |
68 |
69 | {title}
70 |
71 |
76 | {subtitle}
77 |
78 |
79 |
80 |
85 |
86 |
91 | {description}
92 |
93 |
94 |
95 |
96 | );
97 | }
98 |
99 | Chart.propTypes = {
100 | description: PropTypes.string,
101 | subtitle: PropTypes.string,
102 | title: PropTypes.string,
103 | chartSrc: PropTypes.string,
104 | };
105 |
106 | Chart.defaultProps = {
107 | description: undefined,
108 | subtitle: undefined,
109 | title: undefined,
110 | chartSrc: undefined,
111 | };
112 |
113 | export default Chart;
114 |
--------------------------------------------------------------------------------
/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button, Grid } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 | import { useSession, signIn } from 'next-auth/client';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | root: {
9 | flexGrow: 1,
10 | color: 'white',
11 | background: 'none',
12 | },
13 | loginButton: {
14 | marginBottom: '1rem',
15 | width: '100%',
16 | color: 'white',
17 | '&:hover': {
18 | color: theme.palette.secondary.main,
19 | },
20 | backgroundColor: theme.palette.secondary.dark,
21 | fontWeight: 800,
22 | fontSize: theme.typography.subtitle2.fontSize,
23 | height: '3rem',
24 | [theme.breakpoints.up('lg')]: {
25 | fontSize: theme.typography.subtitle1.fontSize,
26 | height: '3.5rem',
27 | paddingLeft: '2rem',
28 | paddingRight: '2rem',
29 | },
30 | },
31 | buttonContainer: {
32 | paddingTop: '1rem',
33 | width: '400px',
34 | [theme.breakpoints.between('xs', 'xs')]: {
35 | width: '95vw',
36 | },
37 | '& .MuiLink-underlineHover': {
38 | '&:hover': {
39 | textDecoration: 'none',
40 | },
41 | },
42 | },
43 | footerInput: {
44 | // Moved to `App.css` due to difficult of setting input `text-align` to `center`
45 | },
46 | buttonLink: {
47 | textDecoration: 'none',
48 | },
49 | formStyles: {
50 | display: 'flex',
51 | flexDirection: 'column',
52 | alignItems: 'center',
53 | },
54 | formControlStyles: {
55 | width: '400px',
56 | marginBottom: '10px',
57 | [theme.breakpoints.between('xs', 'xs')]: {
58 | width: '95vw',
59 | },
60 | },
61 | inputLabel: {
62 | textAlign: 'initial',
63 | color: '#FFFFFF',
64 | transform: 'none',
65 | position: 'static',
66 | },
67 | }));
68 |
69 | function Login({ providers, ...props }) {
70 | const classes = useStyles(props);
71 | const [session, loading] = useSession();
72 | return (
73 |
79 |
80 |
100 |
101 |
102 | );
103 | }
104 |
105 | export default Login;
106 |
--------------------------------------------------------------------------------
/src/components/SocialMedia.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PropTypes from 'prop-types';
4 |
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6 |
7 | import Grid from '@material-ui/core/Grid';
8 | import { makeStyles } from '@material-ui/core/styles';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | root: {
12 | flexGrow: 1,
13 | color: 'white',
14 | backgroundColor: theme.palette.secondary.main,
15 | paddingTop: theme.spacing(1),
16 | paddingBottom: theme.spacing(2),
17 | },
18 | fa: {
19 | transition: 'all .5s ease-in-out',
20 | padding: theme.spacing(0.5),
21 | '&:hover': {
22 | transform: 'scale(1.3)',
23 | color: '#f3f3f3',
24 | },
25 | },
26 | links: {
27 | color: 'white',
28 | },
29 | }));
30 |
31 | function SocialMedia({ color }) {
32 | const classes = useStyles();
33 | return (
34 |
40 |
41 |
47 |
54 |
55 |
56 |
57 |
63 |
70 |
71 |
72 |
73 |
79 |
86 |
87 |
93 |
100 |
101 |
102 |
103 | );
104 | }
105 |
106 | SocialMedia.propTypes = {
107 | color: PropTypes.string,
108 | };
109 |
110 | SocialMedia.defaultProps = {
111 | color: '#fff',
112 | };
113 |
114 | export default SocialMedia;
115 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/ShareButton/SocialMediaButtons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Grid from '@material-ui/core/Grid';
5 | import { makeStyles } from '@material-ui/core/styles';
6 | import { TwitterShareButton } from 'react-share';
7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8 |
9 | const useStyles = makeStyles({
10 | twitter: { backgroundColor: '#00aced', margin: '0.2rem' },
11 | facebook: { backgroundColor: '#3b5998', margin: '0.2rem' },
12 | instagram: { backgroundColor: '#8a3ab9', margin: '0.2rem' },
13 | medium: { backgroundColor: '#00ab6c', margin: '0.2rem' },
14 | fa: { color: 'white', margin: '0.2rem' },
15 | });
16 |
17 | function SocialMediaButtons({ city }) {
18 | const classes = useStyles();
19 | return (
20 |
21 |
22 |
29 |
34 |
40 |
41 |
42 |
43 |
44 |
49 |
55 |
56 |
57 |
58 |
63 |
69 |
70 |
71 |
72 |
77 |
83 |
84 |
85 |
86 | );
87 | }
88 |
89 | SocialMediaButtons.propTypes = {
90 | city: PropTypes.shape({
91 | name: PropTypes.string.isRequired,
92 | twitterHandle: PropTypes.string.isRequired,
93 | }).isRequired,
94 | };
95 | export default SocialMediaButtons;
96 |
--------------------------------------------------------------------------------
/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Document, { Head, Html, Main, NextScript } from 'next/document';
4 | import { ServerStyleSheets } from '@material-ui/core/styles';
5 |
6 | import theme from 'theme';
7 |
8 | import { GA_TRACKING_ID } from 'lib/gtag';
9 |
10 | function getGtagScript() {
11 | return {
12 | __html: `
13 | window.dataLayer = window.dataLayer || [];
14 | function gtag() {
15 | dataLayer.push(arguments);
16 | }
17 | gtag('js', new Date());
18 |
19 | gtag('config', '${GA_TRACKING_ID}', {
20 | page_path: window.location.pathname
21 | });
22 | `,
23 | };
24 | }
25 |
26 | export default class MainDocument extends Document {
27 | render() {
28 | return (
29 |
30 |
31 |
32 |
37 |
41 |
42 |
43 | {/* Global site tag (gtag.js) - Google Analytics */}
44 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | }
61 | }
62 |
63 | MainDocument.getInitialProps = async (ctx) => {
64 | // Resolution order
65 | //
66 | // On the server:
67 | // 1. app.getInitialProps
68 | // 2. page.getInitialProps
69 | // 3. document.getInitialProps
70 | // 4. app.render
71 | // 5. page.render
72 | // 6. document.render
73 | //
74 | // On the server with error:
75 | // 1. document.getInitialProps
76 | // 2. app.render
77 | // 3. page.render
78 | // 4. document.render
79 | //
80 | // On the client
81 | // 1. app.getInitialProps
82 | // 2. page.getInitialProps
83 | // 3. app.render
84 | // 4. page.render
85 |
86 | // Render app and page and get the context of the page with collected side effects.
87 | const sheets = new ServerStyleSheets();
88 | const originalRenderPage = ctx.renderPage;
89 |
90 | ctx.renderPage = () =>
91 | originalRenderPage({
92 | enhanceApp: (App) => (props) => sheets.collect(),
93 | });
94 |
95 | const initialProps = await Document.getInitialProps(ctx);
96 |
97 | return {
98 | ...initialProps,
99 | // Styles fragment is rendered after the app and page rendering finish.
100 | styles: [
101 | ...React.Children.toArray(initialProps.styles),
102 | sheets.getStyleElement(),
103 | ],
104 | };
105 | };
106 |
--------------------------------------------------------------------------------
/src/components/Insights.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, Typography } from '@material-ui/core';
4 | import { makeStyles } from '@material-ui/core/styles';
5 |
6 | import Link from 'components/Link';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | root: {
10 | flexGrow: 1,
11 | backgroundColor: '#F3F3F3',
12 | margin: '0 auto',
13 | marginTop: theme.typography.pxToRem(50),
14 | padding: `${theme.typography.pxToRem(48)} 0`,
15 | width: '100%',
16 | },
17 | section: {
18 | backgroundColor: 'inherit',
19 | marginTop: theme.typography.pxToRem(24),
20 | width: '100%',
21 | [theme.breakpoints.up('md')]: {
22 | width: '59.625rem',
23 | },
24 | [theme.breakpoints.up('lg')]: {
25 | width: '76.125rem',
26 | },
27 | },
28 | text: {},
29 | sectionText: {
30 | marginTop: '2rem',
31 | },
32 | title: {
33 | position: 'relative',
34 | bottom: '30px',
35 | [theme.breakpoints.down('sm')]: {
36 | fontSize: '40px',
37 | bottom: '20px',
38 | },
39 | [theme.breakpoints.down('xs')]: {
40 | bottom: '60px',
41 | },
42 | },
43 | apiLink: {
44 | color: '#424143',
45 | '&:hover': {
46 | textDecoration: 'none',
47 | },
48 | },
49 | background: {
50 | backgroundColor: 'rgba(213, 145, 193, 1)',
51 | width: '250px',
52 | borderRadius: '15px',
53 | padding: '5px 20px',
54 | '&:hover': {
55 | backgroundColor: 'rgba(213, 145, 193, 0.6)',
56 | },
57 | },
58 | underline: {
59 | backgroundColor: 'rgba(47, 181, 107, 0.5)',
60 | height: '40px',
61 | maxWidth: '800px',
62 | borderRadius: '10px',
63 | [theme.breakpoints.between('sm', 'sm')]: {
64 | maxWidth: '600px',
65 | },
66 | [theme.breakpoints.between('xs', 'xs')]: {
67 | margin: '0 20px',
68 | marginTop: '30px',
69 | },
70 | },
71 | }));
72 |
73 | function Insights(props) {
74 | const classes = useStyles(props);
75 |
76 | return (
77 |
78 |
79 |
80 | Do you want to play with the data?
81 |
82 |
83 |
84 |
85 |
86 | Our data can give you granular real-time insights. Use the dashboard
87 | API toolkit to access the raw real-time data, and extract granular
88 | insights or reports.
89 |
90 |
91 |
92 |
93 |
94 | Use the API here
95 | {' '}
96 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | }
104 |
105 | Insights.propTypes = {};
106 | Insights.defaultProps = {};
107 |
108 | export default Insights;
109 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classNames from 'classnames';
4 |
5 | import Router from 'next/router';
6 |
7 | import { Button, Grid, Typography } from '@material-ui/core';
8 | import { makeStyles } from '@material-ui/core/styles';
9 |
10 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11 | import { faAngleLeft } from '@fortawesome/free-solid-svg-icons';
12 |
13 | import DocumentHead, { URLS } from 'components/DocumentHead';
14 | import Navbar from 'components/Header/Navbar';
15 | import Footer from 'components/Footer/index';
16 |
17 | import backgroundImage from 'assets/images/background/bgsupport.jpg';
18 |
19 | const useStyles = makeStyles((theme) => ({
20 | root: {
21 | flexGrow: 1,
22 | backgroundColor: 'white',
23 | backgroundImage: `url(${backgroundImage})`,
24 | },
25 | bodyCopy: {
26 | margin: '8rem',
27 | textAlign: 'center',
28 | },
29 | button: {
30 | color: theme.palette.secondary.main,
31 | backgroundColor: '#fff',
32 | border: `1px solid ${theme.palette.secondary.main}`,
33 | margin: '1rem',
34 | '& .button-icon': {
35 | display: 'none',
36 | },
37 | '&:hover': {
38 | color: theme.palette.secondary.main,
39 | backgroundColor: '#fff',
40 | border: `1px solid ${theme.palette.secondary.main}`,
41 | },
42 | '&:hover .button-icon': {
43 | display: 'inline-block',
44 | },
45 | },
46 | buttonIcon: {
47 | marginRight: '0.5rem',
48 | },
49 | typography: {
50 | color: 'white',
51 | },
52 | }));
53 |
54 | function NotFound(props) {
55 | const classes = useStyles(props);
56 | const handleBack = () => {
57 | // For security and privacy reasons, browsers don't allow JS to view
58 | // visited URLs. We'll use document.referrer for approximation
59 | // @see: https://developer.mozilla.org/en-US/docs/Web/API/Document/referrer
60 | const previous = document.referrer;
61 | if (previous) {
62 | const { location } = window;
63 | if (!location.origin) {
64 | location.origin = `${location.protocol}//${location.host}`;
65 | }
66 | if (previous.startsWith(location.origin)) {
67 | return Router.back();
68 | }
69 | }
70 | return Router.push('/');
71 | };
72 |
73 | return (
74 |
75 |
76 |
77 |
86 |
87 | 404
88 |
89 |
90 | OOPS! WE CAN'T SEEM TO FIND THE PAGE YOU ARE LOOKING FOR.
91 |
92 |
93 |
94 |
105 |
106 |
107 |
108 |
109 | );
110 | }
111 |
112 | export default NotFound;
113 |
--------------------------------------------------------------------------------
/src/components/Hambuger/HambugerMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PropTypes from 'prop-types';
4 |
5 | import { makeStyles } from '@material-ui/core/styles';
6 | import { Grid, MenuItem, Typography, MenuList } from '@material-ui/core';
7 | import Link from 'components/Link';
8 | import Modal from '@material-ui/core/Modal';
9 | import { signOut } from 'next-auth/client';
10 |
11 | import MenuButton from 'components/Hambuger/MenuButton';
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | grid: {
15 | flex: 1,
16 | },
17 | menuList: {
18 | color: 'white',
19 | marginTop: '5rem !important', // Override the default marginTop:'2rem' of Component
20 | textAlign: 'right',
21 | [theme.breakpoints.up('sm')]: {
22 | marginTop: '2rem',
23 | paddingRight: '10%',
24 | },
25 | },
26 | menuListItem: {
27 | color: 'white',
28 | display: 'block',
29 | },
30 | modalContent: {
31 | margin: 'auto',
32 | padding: '20px',
33 | height: 'auto',
34 | },
35 | typography: {
36 | color: '#fff',
37 | textAlign: 'right',
38 | fontWeight: '700',
39 | },
40 | link: {
41 | textDecoration: 'none',
42 | '&:hover': {
43 | textDecoration: 'none',
44 | },
45 | },
46 | }));
47 |
48 | function HambugerMenu({ handleToggle, menuOpen }) {
49 | const classes = useStyles();
50 | return (
51 |
52 |
53 |
58 |
59 |
60 |
66 |
67 |
68 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
100 |
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | HambugerMenu.propTypes = {
108 | handleToggle: PropTypes.func.isRequired,
109 | menuOpen: PropTypes.bool.isRequired,
110 | };
111 |
112 | export default HambugerMenu;
113 |
--------------------------------------------------------------------------------
/src/components/Showcase/StoryList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Grid, GridList, GridListTile } from '@material-ui/core';
4 | import Tabletop from 'tabletop';
5 |
6 | import { withStyles } from '@material-ui/core/styles';
7 |
8 | import StoryCard from './StoryCard';
9 |
10 | const styles = (theme) => ({
11 | root: {
12 | flexGrow: 1,
13 | },
14 | gridListRoot: {
15 | display: 'flex',
16 | flexWrap: 'wrap',
17 | justifyContent: 'space-around',
18 | overflow: 'hidden',
19 | width: '100vw',
20 | height: '100%',
21 | [theme.breakpoints.up('md')]: {
22 | width: '59.625rem',
23 | },
24 | [theme.breakpoints.up('lg')]: {
25 | width: '79.5rem',
26 | },
27 | },
28 | gridList: {
29 | flexWrap: 'nowrap',
30 |
31 | // TODO(nyokabi): Material-ui documentation for Grid list componenet
32 | // Promote the list into his own layer on Chrome. This cost
33 | // memory but helps keeping high FPS.
34 | transform: 'translateZ(0)',
35 | },
36 | gridListTile: {
37 | display: 'flex',
38 | alignItems: 'strech',
39 | width: '100vw',
40 | [theme.breakpoints.up('md')]: {
41 | width: '19.875rem',
42 | },
43 | [theme.breakpoints.up('lg')]: {
44 | width: '26.5rem',
45 | },
46 | },
47 | });
48 |
49 | class StoryList extends React.Component {
50 | constructor(props) {
51 | super(props);
52 |
53 | this.state = { stories: [] };
54 | this.processData = this.processData.bind(this);
55 | }
56 |
57 | componentDidMount() {
58 | Tabletop.init({
59 | key: '1I2nTG_lst4nYrg8z1e7RaolC16A-M7f_lO_zRaV9L5s',
60 | callback: (data) => {
61 | this.processData(data);
62 | },
63 | simpleSheet: true,
64 | });
65 | }
66 |
67 | processData(data) {
68 | /* eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["data"] }] */
69 | for (let i = 0; i < data.length; i += 1) {
70 | data[i].id = i;
71 | }
72 | this.setState({ stories: data });
73 | }
74 |
75 | render() {
76 | const { classes } = this.props;
77 | const { stories } = this.state;
78 |
79 | // TODO(kilemensi): GridListTile computes the size of item and sets it using
80 | // style. This means we can't use classes since element
81 | // style has higher preference. Hence the use of style here.
82 | // We need to match exact size of StoryCard so we don't end
83 | // up with a lot of spaces around StoryCard.
84 | return (
85 |
91 |
92 |
93 |
94 | {stories.map((story) => (
95 |
103 |
104 |
105 | ))}
106 |
107 |
108 |
109 |
110 | );
111 | }
112 | }
113 |
114 | export default withStyles(styles)(StoryList);
115 |
--------------------------------------------------------------------------------
/src/components/City/HostSensors/ShareButton/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Dialog from '@material-ui/core/Dialog';
5 | import MuiDialogTitle from '@material-ui/core/DialogTitle';
6 | import MuiDialogContent from '@material-ui/core/DialogContent';
7 | import IconButton from '@material-ui/core/IconButton';
8 | import Typography from '@material-ui/core/Typography';
9 | import Grid from '@material-ui/core/Grid';
10 | import { withStyles } from '@material-ui/core/styles';
11 |
12 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
13 | import { faTimes } from '@fortawesome/free-solid-svg-icons';
14 |
15 | import HostSensorButton from 'components/City/HostSensors/HostSensorButton';
16 | import SocialMediaButtons from 'components/City/HostSensors/ShareButton/SocialMediaButtons';
17 | import Embed from 'components/City/HostSensors/ShareButton/Embed';
18 |
19 | const styles = () => ({
20 | typography: {
21 | textAlign: 'center',
22 | paddingBottom: '0.5rem',
23 | fontWeight: 600,
24 | },
25 | });
26 |
27 | const DialogTitle = withStyles((theme) => ({
28 | root: {
29 | margin: 0,
30 | paddingTop: '4rem',
31 | textAlign: 'center',
32 | backgroundColor: 'white',
33 | },
34 | closeButton: {
35 | position: 'absolute',
36 | right: theme.spacing(1),
37 | top: theme.spacing(1),
38 | color: theme.palette.secondary.main,
39 | },
40 | }))((props) => {
41 | const { children, classes, onClose } = props;
42 | return (
43 |
44 | {children}
45 | {onClose ? (
46 |
51 |
52 |
53 | ) : null}
54 |
55 | );
56 | });
57 |
58 | const DialogContent = withStyles((theme) => ({
59 | root: {
60 | margin: 0,
61 | padding: theme.spacing(2),
62 | backgroundColor: 'white',
63 | border: '0px',
64 | },
65 | }))(MuiDialogContent);
66 |
67 | function ShareButton({ classes, city }) {
68 | const [open, setOpen] = useState(false);
69 | const handleOpen = () => {
70 | setOpen(true);
71 | };
72 | const handleClose = () => {
73 | setOpen(false);
74 | };
75 |
76 | return (
77 | <>
78 | Share
79 |
105 | >
106 | );
107 | }
108 |
109 | ShareButton.propTypes = {
110 | city: PropTypes.shape({}).isRequired,
111 | };
112 |
113 | export default withStyles(styles)(ShareButton);
114 |
--------------------------------------------------------------------------------